hyde_admin 0.0.11 → 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.
data/bin/hyde_admin.ru CHANGED
@@ -5,7 +5,8 @@ require 'yaml'
5
5
  require 'fileutils'
6
6
  require 'i18n'
7
7
  require 'date'
8
- require 'escape_utils'
8
+ require 'cgi'
9
+ require 'shellwords'
9
10
  require 'image_processing/mini_magick'
10
11
  require_relative '../lib/hyde_admin/version'
11
12
 
@@ -37,14 +38,14 @@ class Mid < Roda
37
38
  if !File.exist?(yml_in_current_dir)
38
39
  FileUtils.cp(yml_in_gem, yml_in_current_dir)
39
40
  end
40
- @hyde_parameters ||= YAML.load(File.read(yml_in_current_dir))
41
+ @hyde_parameters ||= YAML.safe_load(File.read(yml_in_current_dir), permitted_classes: [Date, Time])
41
42
 
42
43
  super(param)
43
44
  end
44
45
 
45
46
  def self.transliterate_title_for_url(title)
46
47
  I18n.config.available_locales = :en
47
- I18n.transliterate(title).downcase.gsub(/[^a-zA-Z ]/,'').gsub(' ','-')
48
+ I18n.transliterate(title).downcase.gsub(/[^a-zA-Z0-9 ]/,'').strip.gsub(/\s+/,'-').gsub(/-+/,'-').chomp('-')
48
49
  end
49
50
 
50
51
  def self.urlize(date_str, title, with_date = true)
@@ -62,7 +63,7 @@ class Mid < Roda
62
63
 
63
64
  def self.extract_header(str)
64
65
  headers = Mid.extract_header_str(str).to_s.split("\n")
65
- headers = headers.select{ |header| !header.empty? }.map{ |header| header.scan(/([a-zA-Z0-9]*): (.*)/).flatten }.select{ |header| !header.empty? }
66
+ headers = headers.select{ |header| !header.empty? }.map{ |header| header.scan(/([a-zA-Z0-9_-]+): (.*)/).flatten }.select{ |header| !header.empty? }
66
67
  hsh_headers = {}
67
68
  if !headers.flatten.empty?
68
69
  #$stderr.puts "==============="
@@ -80,6 +81,10 @@ class Mid < Roda
80
81
  File.expand_path(File.dirname(__FILE__))
81
82
  end
82
83
 
84
+ def self.safe_path?(path)
85
+ File.expand_path(path).start_with?(Dir.pwd)
86
+ end
87
+
83
88
  def self.extract_tags(str)
84
89
  str.to_s.scan(/^\[?(.*?)\]?$/).flatten.first.split(',')
85
90
  end
@@ -112,6 +117,7 @@ class Mid < Roda
112
117
 
113
118
  route do |r|
114
119
  @page = r.params['page']
120
+ @notice = r.params['notice']
115
121
 
116
122
  if @hyde_parameters['hyde_admin_auth'].to_s == 'true'
117
123
  http_auth {|u, p| [u, p] == [@hyde_parameters['hyde_admin_user'], @hyde_parameters['hyde_admin_password']] }
@@ -134,13 +140,46 @@ class Mid < Roda
134
140
  # Rebuild static files
135
141
  r.on "rebuild" do
136
142
  $stderr.puts Dir.pwd
137
- $stderr.puts `cd #{Dir.pwd} && jekyll b`
138
- r.redirect "/dashboard"
143
+ $stderr.puts `cd #{Shellwords.escape(Dir.pwd)} && jekyll b`
144
+ r.redirect "/dashboard?notice=#{CGI.escape(t.rebuild + ' OK')}"
139
145
  end
140
146
 
141
147
  r.on "deploy" do
142
- `#{@hyde_parameters['rsync_fullpath']} -avzr #{Dir.pwd}/_site/ #{@hyde_parameters['deploy_dest_user']}@#{@hyde_parameters['deploy_dest_address']}:#{@hyde_parameters['deploy_dest_path']}`
143
- r.redirect "/dashboard"
148
+ `#{Shellwords.escape(@hyde_parameters['rsync_fullpath'])} -avzr #{Shellwords.escape("#{Dir.pwd}/_site/")} #{Shellwords.escape(@hyde_parameters['deploy_dest_user'])}@#{Shellwords.escape(@hyde_parameters['deploy_dest_address'])}:#{Shellwords.escape(@hyde_parameters['deploy_dest_path'])}`
149
+ r.redirect "/dashboard?notice=#{CGI.escape(t.deploy + ' OK')}"
150
+ end
151
+
152
+ r.on "deploy_sftp" do
153
+ site_dir = "#{Dir.pwd}/_site"
154
+ sftp_user = @hyde_parameters['sftp_user']
155
+ sftp_host = @hyde_parameters['sftp_host']
156
+ sftp_port = @hyde_parameters['sftp_port'].to_s.empty? ? '22' : @hyde_parameters['sftp_port']
157
+ sftp_path = @hyde_parameters['sftp_dest_path']
158
+ sftp_password = @hyde_parameters['sftp_password']
159
+
160
+ batch_commands = []
161
+ Dir.glob("#{site_dir}/**/*").each do |local_path|
162
+ relative = local_path.sub("#{site_dir}/", '')
163
+ remote = "#{sftp_path}/#{relative}"
164
+ if File.directory?(local_path)
165
+ batch_commands << "-mkdir #{Shellwords.escape(remote)}"
166
+ else
167
+ batch_commands << "put #{Shellwords.escape(local_path)} #{Shellwords.escape(remote)}"
168
+ end
169
+ end
170
+
171
+ batch_file = File.join(Dir.pwd, '.sftp_batch')
172
+ File.write(batch_file, batch_commands.join("\n"))
173
+
174
+ sftp_cmd = "sftp -P #{Shellwords.escape(sftp_port)} -b #{Shellwords.escape(batch_file)}"
175
+ if !sftp_password.to_s.empty?
176
+ sftp_cmd = "sshpass -p #{Shellwords.escape(sftp_password)} #{sftp_cmd}"
177
+ end
178
+ sftp_cmd += " #{Shellwords.escape(sftp_user)}@#{Shellwords.escape(sftp_host)}"
179
+
180
+ `#{sftp_cmd}`
181
+ File.delete(batch_file) if File.exist?(batch_file)
182
+ r.redirect "/dashboard?notice=#{CGI.escape(t.deploy_sftp + ' OK')}"
144
183
  end
145
184
 
146
185
  r.post "configuration" do
@@ -151,7 +190,7 @@ class Mid < Roda
151
190
  File.open(File.join(Dir.pwd, YML_FILE_NAME),"w+") do |f|
152
191
  f.write(@hyde_parameters.to_yaml)
153
192
  end
154
- r.redirect "/configuration"
193
+ r.redirect "/configuration?notice=#{CGI.escape(t.configuration + ' OK')}"
155
194
  end
156
195
 
157
196
  r.get "configuration" do
@@ -162,6 +201,79 @@ class Mid < Roda
162
201
  view("dashboard")
163
202
  end
164
203
 
204
+ r.get "search" do
205
+ @query = (r.params['q'] || '').strip
206
+ @results = []
207
+
208
+ if @query.length >= 2
209
+ normalized_query = @query.unicode_normalize(:nfkd).gsub(/\p{Mn}/, '').downcase
210
+
211
+ %w[_posts _pages _drafts].each do |dir|
212
+ type_name = dir.sub('_', '')
213
+ dir_path = File.join(Dir.pwd, dir)
214
+ next unless File.directory?(dir_path)
215
+
216
+ Dir.glob(File.join(dir_path, '**', '*')).select { |f| File.file?(f) }.each do |f|
217
+ content = File.read(f, encoding: 'utf-8') rescue next
218
+ headers = Mid.extract_header(content)
219
+ body = Mid.remove_header(content)
220
+ relative = f.gsub(File.join(Dir.pwd, ''), '')
221
+ searchable = [
222
+ File.basename(f),
223
+ headers['title'].to_s,
224
+ headers['tags'].to_s,
225
+ body.to_s[0, 500]
226
+ ].join(' ').unicode_normalize(:nfkd).gsub(/\p{Mn}/, '').downcase
227
+
228
+ if searchable.include?(normalized_query)
229
+ excerpt = nil
230
+ body_normalized = body.to_s.unicode_normalize(:nfkd).gsub(/\p{Mn}/, '').downcase
231
+ pos = body_normalized.index(normalized_query)
232
+ if pos
233
+ start = [pos - 60, 0].max
234
+ excerpt = body.to_s[start, 150].gsub(/\s+/, ' ').strip
235
+ excerpt = "...#{excerpt}..." if start > 0
236
+ end
237
+
238
+ @results << {
239
+ type: type_name,
240
+ path: f,
241
+ relative: relative,
242
+ title: headers['title'].to_s.empty? ? File.basename(f) : headers['title'],
243
+ tags: headers['tags'].to_s,
244
+ excerpt: excerpt,
245
+ edit_url: "/#{type_name}?file=#{CGI.escape(f)}"
246
+ }
247
+ end
248
+ end
249
+ end
250
+
251
+ # Search in files (root directory, excluding _ dirs and _site)
252
+ Dir.glob(File.join(Dir.pwd, '**', '*')).select { |f|
253
+ File.file?(f) &&
254
+ !f.include?('/_') &&
255
+ !f.include?('/_site') &&
256
+ !f.start_with?(File.join(Dir.pwd, '_'))
257
+ }.each do |f|
258
+ relative = f.gsub(File.join(Dir.pwd, ''), '')
259
+ searchable = File.basename(f).unicode_normalize(:nfkd).gsub(/\p{Mn}/, '').downcase
260
+ if searchable.include?(normalized_query)
261
+ @results << {
262
+ type: 'files',
263
+ path: f,
264
+ relative: relative,
265
+ title: File.basename(f),
266
+ tags: '',
267
+ excerpt: nil,
268
+ edit_url: "/files/edit?dir_path=#{CGI.escape(File.dirname(f))}&file=#{CGI.escape(f)}"
269
+ }
270
+ end
271
+ end
272
+ end
273
+
274
+ view("search")
275
+ end
276
+
165
277
  r.on "upload_image_form" do
166
278
  render("upload_image_form")
167
279
  end
@@ -171,8 +283,12 @@ class Mid < Roda
171
283
  @filenames = []
172
284
  files.each do |file|
173
285
  filename = file[:filename]
286
+ ext = File.extname(filename)
287
+ base = File.basename(filename, ext)
288
+ counter = 1
174
289
  while File.exist?(File.join(@hyde_parameters['images_path'], filename))
175
- filename = "#{File.basename(filename, File.extname(filename))}_#{File.extname(filename)}"
290
+ filename = "#{base}_#{counter}#{ext}"
291
+ counter += 1
176
292
  end
177
293
  @filenames << filename
178
294
  File.open(File.join(@hyde_parameters['images_path'], filename), 'wb') do |f|
@@ -187,6 +303,7 @@ class Mid < Roda
187
303
 
188
304
  r.on "files" do
189
305
  @dir_path = r.params['dir_path'] || Dir.pwd
306
+ response.status = 403 and next unless Mid.safe_path?(@dir_path)
190
307
 
191
308
  # List files
192
309
  r.get "index" do
@@ -225,6 +342,7 @@ class Mid < Roda
225
342
  # Edit text files
226
343
  r.get "edit" do
227
344
  @file = r.params['file']
345
+ response.status = 403 and next unless Mid.safe_path?(@file)
228
346
  @content = File.read(@file)
229
347
  @header = Mid.extract_header_str(@content)
230
348
  @content = Mid.remove_header(@content)
@@ -236,6 +354,7 @@ class Mid < Roda
236
354
  # Update file
237
355
  r.post "update" do
238
356
  @file = r.params['file']
357
+ response.status = 403 and next unless Mid.safe_path?(@file)
239
358
  @content = r.params['content']
240
359
  @header = r.params['header']
241
360
  File.open(@file,"w+") do |f|
@@ -253,7 +372,12 @@ class Mid < Roda
253
372
  # Delete
254
373
  r.post "delete" do
255
374
  file = r.params['file']
256
- File.unlink(file)
375
+ response.status = 403 and next unless Mid.safe_path?(file)
376
+ if File.directory?(file)
377
+ FileUtils.rm_rf(file) if Dir[File.join(file, '*')].empty?
378
+ else
379
+ File.unlink(file)
380
+ end
257
381
  r.redirect "/files/index?dir_path=#{@dir_path}"
258
382
  end
259
383
  end
@@ -276,6 +400,34 @@ class Mid < Roda
276
400
  date = Time.now.strftime(FORMAT_DATE_INPUT_FILENAME)
277
401
  response.write(date)
278
402
  end
403
+ r.post "preview" do
404
+ content = r.params['content']
405
+ title = r.params['title']
406
+ date = r.params['date']
407
+ layout = r.params['layout']
408
+ tags = r.params['tags']
409
+ format = r.params['format'] || 'md'
410
+
411
+ headers = ["---"]
412
+ headers << "title: #{title}" unless title.to_s.empty?
413
+ headers << "date: #{date}" unless date.to_s.empty?
414
+ headers << "layout: #{layout}" unless layout.to_s.empty?
415
+ headers << "tags: #{tags}" unless tags.to_s.empty?
416
+ headers << "---"
417
+ headers << ""
418
+
419
+ preview_file = File.join(Dir.pwd, "hyde-preview-tmp.#{format}")
420
+ File.open(preview_file, "w+") do |f|
421
+ f.write(headers.join("\n"))
422
+ f.write(content)
423
+ end
424
+
425
+ `cd #{Shellwords.escape(Dir.pwd)} && jekyll b 2>&1`
426
+
427
+ File.delete(preview_file) if File.exist?(preview_file)
428
+
429
+ response.write("/hyde-preview-tmp.html?t=#{Time.now.to_i}")
430
+ end
279
431
  r.post "images" do
280
432
  nb_elements_per_page = 9
281
433
 
@@ -325,6 +477,7 @@ class Mid < Roda
325
477
  # save the truc post
326
478
  r.post "delete" do
327
479
  @file = r.params['file']
480
+ response.status = 403 and next unless Mid.safe_path?(@file)
328
481
  File.unlink(@file)
329
482
  r.redirect "/#{@type_file}/index?dir_path=#{File.dirname(@file)}"
330
483
  end
@@ -334,6 +487,7 @@ class Mid < Roda
334
487
  # edit the truc post
335
488
  r.get do
336
489
  @file = r.params['file']
490
+ response.status = 403 and next unless Mid.safe_path?(@file)
337
491
 
338
492
  content_file = File.read(@file)
339
493
  @headers = Mid.extract_header(content_file)
data/bin/hyde_admin.yml CHANGED
@@ -15,4 +15,5 @@ display_format: 'true'
15
15
  images_path: assets/images/
16
16
  resize_format: jpg
17
17
  resize_size: 1500x1000
18
- resize_enable: 'true'
18
+ resize_enable: 'true'
19
+ text_editor_maximized: 'true'
@@ -41,11 +41,69 @@
41
41
  max-width: 90vw;
42
42
  }
43
43
 
44
+ #autosave-indicator {
45
+ display: inline-block;
46
+ margin-left: 12px;
47
+ font-size: 0.85em;
48
+ color: #6c757d;
49
+ opacity: 0;
50
+ transition: opacity 0.3s ease;
51
+ }
52
+ #autosave-indicator.visible {
53
+ opacity: 1;
54
+ }
55
+
44
56
  .CodeMirror{
45
57
  border: 1px solid #ced4da;
46
58
  border-radius: 4px;
47
59
  }
48
60
 
61
+ /* Preview split-screen */
62
+ #preview-wrapper.preview-active {
63
+ display: flex;
64
+ flex-direction: column;
65
+ height: calc(100vh - 56px);
66
+ }
67
+ #preview-wrapper.preview-active #editor-panel {
68
+ flex: 1;
69
+ overflow-y: auto;
70
+ min-height: 0;
71
+ padding-right: 8px;
72
+ }
73
+ #preview-wrapper.preview-active #preview-panel {
74
+ flex: 1;
75
+ display: flex;
76
+ flex-direction: column;
77
+ min-height: 0;
78
+ border-top: 2px solid #dee2e6;
79
+ }
80
+ #preview-panel .preview-panel-header {
81
+ flex-shrink: 0;
82
+ }
83
+ #preview-iframe {
84
+ flex: 1;
85
+ width: 100%;
86
+ height: 100%;
87
+ border: none;
88
+ min-height: 0;
89
+ }
90
+ main.preview-active-main {
91
+ padding: 8px !important;
92
+ overflow: hidden;
93
+ }
94
+ @media (min-width: 1900px) {
95
+ #preview-wrapper.preview-active {
96
+ flex-direction: row;
97
+ }
98
+ #preview-wrapper.preview-active #editor-panel {
99
+ width: 50%;
100
+ }
101
+ #preview-wrapper.preview-active #preview-panel {
102
+ border-top: none;
103
+ border-left: 2px solid #dee2e6;
104
+ }
105
+ }
106
+
49
107
  /* hyde admin style */
50
108
  .sidebarMenu-logo{
51
109
  width: 144px;
@@ -1,24 +1,127 @@
1
- $(function(){
2
- $(document).on('click','#btn-deploy,#btn-rebuild',function(elt){
3
- let path = '';
4
-
5
- if(elt.id === 'btn-deploy'){
1
+ document.addEventListener('DOMContentLoaded', function(){
2
+ document.addEventListener('click', function(e){
3
+ var btn = e.target.closest('#btn-deploy, #btn-rebuild');
4
+ if(!btn) return;
5
+ e.preventDefault();
6
+ var path = '';
7
+ if(btn.id === 'btn-deploy'){
6
8
  path = '/deploy';
7
- }else if(elt.id === 'btn-rebuild'){
9
+ }else if(btn.id === 'btn-rebuild'){
8
10
  path = '/rebuild';
9
11
  }
10
-
11
- $.post( path, {
12
- beforeSend: function( xhr ) {
13
- $('#waiting').show();
14
- }
15
- })
16
- .done(function( data ) {
17
- $('#waiting').hide();
18
- return false;
12
+ document.getElementById('waiting').style.display = 'block';
13
+ fetch(path, { method: 'POST' })
14
+ .finally(function() {
15
+ document.getElementById('waiting').style.display = 'none';
19
16
  });
20
17
  });
21
- $(document).on('click','.form-confirm',function(){
22
- return window.confirm($(this).attr('data-confirm'));
18
+ document.addEventListener('submit', function(e){
19
+ var form = e.target.closest('.form-confirm');
20
+ if(!form) return;
21
+ if(!window.confirm(form.getAttribute('data-confirm'))){
22
+ e.preventDefault();
23
+ }
23
24
  });
24
- });
25
+
26
+ // Autosave
27
+ var autosaveForm = document.querySelector('form[data-autosave-key]');
28
+ if (autosaveForm) {
29
+ var autosaveKey = autosaveForm.getAttribute('data-autosave-key');
30
+ var autosaveInterval = 15000;
31
+ var autosaveTimer = null;
32
+ var autosaveFields = ['i-title', 'i-date', 'i-tags'];
33
+
34
+ function autosaveGetContent() {
35
+ if (window.myCodeMirror) return window.myCodeMirror.getValue();
36
+ var ta = document.getElementById('i-content');
37
+ return ta ? ta.value : '';
38
+ }
39
+
40
+ function autosaveSetContent(val) {
41
+ if (window.myCodeMirror) { window.myCodeMirror.setValue(val); return; }
42
+ var ta = document.getElementById('i-content');
43
+ if (ta) ta.value = val;
44
+ }
45
+
46
+ function autosaveCollect() {
47
+ var data = { _ts: Date.now() };
48
+ autosaveFields.forEach(function(id) {
49
+ var el = document.getElementById(id);
50
+ if (el) data[id] = el.value;
51
+ });
52
+ var layoutEl = autosaveForm.querySelector('select[name="layout"]');
53
+ if (layoutEl) data['layout'] = layoutEl.value;
54
+ data['content'] = autosaveGetContent();
55
+ return data;
56
+ }
57
+
58
+ function autosaveSave() {
59
+ try {
60
+ localStorage.setItem(autosaveKey, JSON.stringify(autosaveCollect()));
61
+ var ind = document.getElementById('autosave-indicator');
62
+ if (ind) {
63
+ ind.classList.add('visible');
64
+ setTimeout(function() { ind.classList.remove('visible'); }, 2000);
65
+ }
66
+ } catch(e) {}
67
+ }
68
+
69
+ function autosaveRestore(data) {
70
+ autosaveFields.forEach(function(id) {
71
+ var el = document.getElementById(id);
72
+ if (el && data[id] !== undefined) el.value = data[id];
73
+ });
74
+ var layoutEl = autosaveForm.querySelector('select[name="layout"]');
75
+ if (layoutEl && data['layout']) layoutEl.value = data['layout'];
76
+ if (data['content'] !== undefined) autosaveSetContent(data['content']);
77
+ }
78
+
79
+ function autosaveShowBanner(data) {
80
+ var banner = document.getElementById('autosave-banner');
81
+ if (!banner) return;
82
+ var dateStr = new Date(data._ts).toLocaleString();
83
+ document.getElementById('autosave-banner-text').textContent =
84
+ 'Autosaved draft from ' + dateStr;
85
+ banner.classList.remove('d-none');
86
+
87
+ document.getElementById('autosave-restore').addEventListener('click', function() {
88
+ autosaveRestore(data);
89
+ banner.classList.add('d-none');
90
+ });
91
+ document.getElementById('autosave-discard').addEventListener('click', function() {
92
+ localStorage.removeItem(autosaveKey);
93
+ banner.classList.add('d-none');
94
+ });
95
+ }
96
+
97
+ function autosaveInit() {
98
+ try {
99
+ var raw = localStorage.getItem(autosaveKey);
100
+ if (raw) {
101
+ var data = JSON.parse(raw);
102
+ if (Date.now() - data._ts < 7 * 24 * 3600 * 1000) {
103
+ autosaveShowBanner(data);
104
+ } else {
105
+ localStorage.removeItem(autosaveKey);
106
+ }
107
+ }
108
+ } catch(e) { localStorage.removeItem(autosaveKey); }
109
+
110
+ autosaveTimer = setInterval(autosaveSave, autosaveInterval);
111
+
112
+ autosaveForm.addEventListener('submit', function() {
113
+ localStorage.removeItem(autosaveKey);
114
+ if (autosaveTimer) clearInterval(autosaveTimer);
115
+ });
116
+ }
117
+
118
+ // Wait for CodeMirror to be ready
119
+ (function waitCM(attempts) {
120
+ if (window.myCodeMirror || attempts <= 0) {
121
+ autosaveInit();
122
+ } else {
123
+ setTimeout(function() { waitCM(attempts - 1); }, 100);
124
+ }
125
+ })(20);
126
+ }
127
+ });
data/bin/i18n/en.yml CHANGED
@@ -44,6 +44,17 @@ deploy_dest_path: deploy remote path
44
44
  help_deploy_dest_path: remote path on disk for ssh deployment
45
45
  rsync_fullpath: rsync path
46
46
  help_rsync_fullpath: if rsync is not in PATH, othervise just let 'rsync'
47
+ deploy_sftp: deploy SFTP
48
+ sftp_user: SFTP user
49
+ help_sftp_user: SFTP user for connection
50
+ sftp_host: SFTP host
51
+ help_sftp_host: SFTP server hostname or IP address
52
+ sftp_port: SFTP port
53
+ help_sftp_port: SFTP port (default 22)
54
+ sftp_dest_path: SFTP remote path
55
+ help_sftp_dest_path: remote path on the SFTP server for deployment
56
+ sftp_password: SFTP password
57
+ help_sftp_password: SFTP password (leave empty to use SSH key authentication)
47
58
  site_index: index file name of site
48
59
  help_site_index: link to open when we click on overview
49
60
  hyde_admin_language: hyde admin language
@@ -83,6 +94,8 @@ editor_underline: Underline
83
94
  editor_bold: Bold
84
95
  editor_italic: Italic
85
96
  editor_strikethrough: Strikethrough
97
+ editor_pre_code: Code
98
+ editor_liquid_code: Liquid code
86
99
  default_alt_img: Alt text
87
100
  default_title_img: Title text
88
101
  parent_dir: parent directory
@@ -106,4 +119,14 @@ help_resize_format: jpg or png
106
119
  resize_size: "size to fit, example : 1500x1000"
107
120
  help_resize_size: Downsize to fit to the specified size (resize preserve ratio), example 1500x1000
108
121
  resize_enable: Enable resizing for images
109
- help_resize_enable: enable feature resize image at upload
122
+ help_resize_enable: enable feature resize image at upload
123
+ text_editor_maximized: enable maximised editor
124
+ help_text_editor_maximized: expand the editor to the maximum of the content
125
+ global_search: search
126
+ global_search_placeholder: search posts, pages, files...
127
+ global_search_no_results: no results
128
+ global_search_results_for: results for
129
+ editor_preview: Preview
130
+ preview_building: Building preview...
131
+ preview_close: Close preview
132
+ preview_refresh: Refresh preview
data/bin/i18n/fr.yml CHANGED
@@ -44,6 +44,17 @@ deploy_dest_path: chemin distant pour déploiement
44
44
  help_deploy_dest_path: chemin distant pour déploiement du site
45
45
  rsync_fullpath: chemin rsync
46
46
  help_rsync_fullpath: si rsync n'est pas dans le PATH, sinon laissez 'rsync'
47
+ deploy_sftp: déployer SFTP
48
+ sftp_user: utilisateur SFTP
49
+ help_sftp_user: utilisateur pour la connexion SFTP
50
+ sftp_host: hôte SFTP
51
+ help_sftp_host: nom de domaine ou adresse IP du serveur SFTP
52
+ sftp_port: port SFTP
53
+ help_sftp_port: port SFTP (par défaut 22)
54
+ sftp_dest_path: chemin distant SFTP
55
+ help_sftp_dest_path: chemin distant sur le serveur SFTP pour le déploiement
56
+ sftp_password: mot de passe SFTP
57
+ help_sftp_password: mot de passe SFTP (laisser vide pour utiliser l'authentification par clé SSH)
47
58
  site_index: nom du fichier index pour le site
48
59
  help_site_index: Lien pour le lien aperçu, pensez à l'extention si nécessaire
49
60
  hyde_admin_language: hyde admin langue
@@ -83,13 +94,15 @@ editor_underline: Souligner
83
94
  editor_bold: Gras
84
95
  editor_italic: Italique
85
96
  editor_strikethrough: Barré
97
+ editor_pre_code: Code
98
+ editor_liquid_code: Liquid code
86
99
  default_alt_img: Texte alternatif
87
100
  default_title_img: Titre image
88
101
  parent_dir: dossier parent
89
102
  sort_by_date: tri par date
90
103
  older: vieille d'abord
91
104
  newer: récente d'abord
92
- previous_images: images précédentes " lol
105
+ previous_images: images précédentes
93
106
  next_images: images suivantes
94
107
  search: recherche
95
108
  filename: nom de fichier
@@ -106,4 +119,14 @@ help_resize_format: jpg ou png
106
119
  resize_size: "Recadrer en (example : 1500x1000)"
107
120
  help_resize_size: Réduit les image pour coller à la taille spécifiée (le redimensionnement préserve le ratio), exemple 1500x1000
108
121
  resize_enable: activer redimensionnement
109
- help_resize_enable: active la fonctionnalité de redimensionnement d'image à l'upload
122
+ help_resize_enable: active la fonctionnalité de redimensionnement d'image à l'upload
123
+ text_editor_maximized: activer l'editeur maximisé
124
+ help_text_editor_maximized: permet d'agrandir l'éditeur au maximum du contenu
125
+ global_search: recherche
126
+ global_search_placeholder: rechercher articles, pages, fichiers...
127
+ global_search_no_results: aucun résultat
128
+ global_search_results_for: résultats pour
129
+ editor_preview: Aperçu
130
+ preview_building: Génération de l'aperçu...
131
+ preview_close: Fermer l'aperçu
132
+ preview_refresh: Rafraîchir l'aperçu
data/hyde_admin.gemspec CHANGED
@@ -25,14 +25,12 @@ Gem::Specification.new do |s|
25
25
  }
26
26
 
27
27
  s.license = 'MIT'
28
- s.add_runtime_dependency("roda", "~> 3.48.0")
29
- s.add_runtime_dependency("roda-i18n", "~> 0.4.0")
30
- s.add_runtime_dependency("roda-http-auth", "0.2.0")
31
- s.add_runtime_dependency("escape_utils") # escape_javascript / escape_html
32
- #s.add_runtime_dependency("i18n", "~> 0.4.0") # I18n.transliterate (already required by jekyll)
33
- s.add_runtime_dependency('jekyll') # Because we call jekyll binary
34
- s.add_runtime_dependency('image_processing') # JPEG quality
28
+ s.add_runtime_dependency("roda", "~> 3.48")
29
+ s.add_runtime_dependency("roda-i18n", "~> 0.4")
30
+ s.add_runtime_dependency("roda-http-auth", "~> 0.2")
31
+ s.add_runtime_dependency('jekyll')
32
+ s.add_runtime_dependency('image_processing')
35
33
  s.add_runtime_dependency('mini_magick')
36
- s.add_runtime_dependency('rack', '2.2.4')
34
+ s.add_runtime_dependency('rack', "~> 2.2")
37
35
  s.add_runtime_dependency('puma')
38
36
  end
@@ -1,3 +1,3 @@
1
1
  module HydeAdmin
2
- VERSION = "0.0.11"
2
+ VERSION = "0.0.14"
3
3
  end