eksa-framework 2.3.2 โ†’ 3.3.3

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: f15480cfb455196634bec205c45a10a83abf031d398915f38652a793f9b98c84
4
- data.tar.gz: 33577ecbf0baf8f1bfb66abfbbf30c39e7cfac37500aefb64d2cabe4d50b2e83
3
+ metadata.gz: ac242785773ed45e2a702cc824fe9407b322eba723af008c531e5e4b79b3a30c
4
+ data.tar.gz: 28ad3240e9d0cd86d1501ba998512d7e201f5bde405db5be6050c53ff4b56b56
5
5
  SHA512:
6
- metadata.gz: 61cc9369d8553779a045858906a451e4370a7121370205a2b2ff1311412f16103b76896b9a206cd629150abe1313ee822a582b8e225d13d6c0bcf2be29ffd942
7
- data.tar.gz: d05d48c0ee928aa8c70942224a003632ac29404e5a588e377724570a51ca1afdb6c3bb83dd5489f0e06287f577c1d39c1cc53ada3f6d5303faa741552dc24a42
6
+ metadata.gz: 1fa366903b127fc04b610d065d1e8fb4d15047e7111e7fbf4ab2332c7a2bd82e2f064dd564088db38d9aa07b25bfadf918fa6f1b08cb2318d45d33a97c27d7ac
7
+ data.tar.gz: b1bb528e37369f1df59f5d54d6209fcc7e1b50cf535e421eeeee41a601faddcc64166bc27f10623ac14ab007fd8ee2755383bddef5052c8753e020f1f6a633f6
data/README.md CHANGED
@@ -18,6 +18,7 @@
18
18
  * ๐Ÿงช **Built-in Testing**: Lingkungan pengujian otomatis siap pakai menggunakan RSpec dan `rack-test`.
19
19
  * ๐ŸŽจ **Asset Helpers**: Library bawaan untuk pengelolaan CSS dan JS yang lebih rapi.
20
20
  * ๐Ÿ” **Dynamic SEO Engine**: Penanganan otomatis file `robots.txt` dan `sitemap.xml`.
21
+ * ๐Ÿ’Ž **JSON-LD Support**: Dukungan data terstruktur (Structured Data) otomatis untuk SEO yang lebih optimal.
21
22
  * ๐Ÿ‘ป **Aesthetic Error Pages**: Halaman 404 dengan desain Glassmorphism yang elegan secara native.
22
23
 
23
24
  ---
@@ -67,8 +68,8 @@ eksa g controller Blog
67
68
  # Membuat model dan schema database
68
69
  eksa g model Post
69
70
 
70
- # Membuat postingan blog baru
71
- eksa g post "Judul Artikel"
71
+ # Membuat postingan blog baru dengan meta tambahan
72
+ eksa g post "Judul Artikel" --category "Kategori" --author "Nama Penulis" --image "url-gambar.jpg"
72
73
  ```
73
74
 
74
75
  ### 3. Markdown Blog Engine
@@ -123,4 +124,4 @@ bundle exec rspec
123
124
  ---
124
125
 
125
126
  ## ๐Ÿ“œ Lisensi
126
- Proyek ini dilisensikan di bawah **MIT License**. Lihat file [LICENSE](LICENSE) untuk detail lebih lanjut.
127
+ Proyek ini dilisensikan di bawah **MIT License**. Lihat file [LICENSE](https://github.com/IshikawaUta/eksa-framework/blob/main/LICENSE) untuk detail lebih lanjut.
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  title: "Welcome To Eksa Framework"
3
3
  date: 2026-03-15 14:09:58
4
+ author: "IshikawaUta"
5
+ category: "Framework"
6
+ image: "https://res.cloudinary.com/dzsqaauqn/image/upload/v1773645204/logo_ne3hy4.png"
4
7
  ---
5
8
 
6
9
  # โœจ Eksa Framework
@@ -1,27 +1,40 @@
1
1
  class SeoController < Eksa::Controller
2
2
  def robots
3
+ scheme = request.env['rack.url_scheme'] || 'https'
3
4
  content = <<~TEXT
4
5
  User-agent: *
5
6
  Allow: /
6
7
  Disallow: /hapus
7
8
  Disallow: /edit
8
9
 
9
- Sitemap: https://#{request.host}/sitemap.xml
10
+ Sitemap: #{scheme}://#{request.host}/sitemap.xml
10
11
  TEXT
11
12
  [200, { "Content-Type" => "text/plain" }, [content]]
12
13
  end
13
14
 
14
15
  def sitemap
16
+ scheme = request.env['rack.url_scheme'] || 'https'
17
+ base_url = "#{scheme}://#{request.host}"
15
18
  lastmod = Time.now.strftime("%Y-%m-%d")
16
19
 
17
20
  xml = '<?xml version="1.0" encoding="UTF-8"?>'
18
21
  xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
19
22
 
20
- ["/", "/about", "/docs", "/kontak"].each do |path|
23
+ # Base Pages
24
+ ["/", "/about", "/docs", "/kontak", "/posts"].each do |path|
21
25
  xml += "<url>"
22
- xml += "<loc>https://#{request.host}#{path}</loc>"
26
+ xml += "<loc>#{base_url}#{path}</loc>"
23
27
  xml += "<lastmod>#{lastmod}</lastmod>"
24
- xml += "<priority>0.8</priority>"
28
+ xml += "<priority>#{path == '/' ? '1.0' : '0.8'}</priority>"
29
+ xml += "</url>"
30
+ end
31
+
32
+ # Blog Posts
33
+ Eksa::MarkdownPost.all.each do |post|
34
+ xml += "<url>"
35
+ xml += "<loc>#{base_url}/posts/#{post.slug}</loc>"
36
+ xml += "<lastmod>#{post.date.is_a?(Time) ? post.date.strftime("%Y-%m-%d") : lastmod}</lastmod>"
37
+ xml += "<priority>0.6</priority>"
25
38
  xml += "</url>"
26
39
  end
27
40
 
@@ -136,8 +136,8 @@
136
136
  </h2>
137
137
  <p class="text-white/60 mb-4 text-sm">Kelola konten blog dengan file Markdown. Mendukung Front Matter YAML dan rute dinamis.</p>
138
138
  <div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
139
- <p class="text-white/30"># Buat post baru lewat CLI</p>
140
- <p class="text-indigo-300">eksa g post "Judul Postingan"</p>
139
+ <p class="text-white/30"># Buat post baru lewat CLI (dengan opsi meta)</p>
140
+ <p class="text-indigo-300">eksa g post "Judul Postingan" --category "Tech" --author "Nama" --image "url.jpg"</p>
141
141
  <p class="text-white/30 mt-4"># Akses di URL (Otomatis)</p>
142
142
  <p class="text-white/70">/posts/:slug</p>
143
143
  </div>
@@ -155,7 +155,7 @@
155
155
  <p class="text-white/30"># Build gem lokal</p>
156
156
  <p class="text-indigo-300">gem build eksa-framework.gemspec</p>
157
157
  <p class="text-white/30 mt-4"># Publish ke RubyGems / GitHub Packages</p>
158
- <p class="text-indigo-300">gem push eksa-framework-2.3.2.gem</p>
158
+ <p class="text-indigo-300">gem push eksa-framework-3.3.3.gem</p>
159
159
  </div>
160
160
  </section>
161
161
 
@@ -164,7 +164,7 @@
164
164
  <span class="bg-indigo-500/20 text-indigo-300 text-[10px] px-3 py-1 rounded-full border border-indigo-500/30 uppercase tracking-tighter">Rack 3.0 Compatible</span>
165
165
  <span class="bg-emerald-500/20 text-emerald-300 text-[10px] px-3 py-1 rounded-full border border-emerald-500/30 uppercase tracking-tighter">SQLite3 Auto-Ready</span>
166
166
  </div>
167
- <p class="text-white/40 text-sm italic">Eksa Framework v2.3.2 Alpha Documentation</p>
167
+ <p class="text-white/40 text-sm italic">Eksa Framework v3.3.3 Alpha Documentation</p>
168
168
  <a href="/" class="inline-block mt-6 text-indigo-300 hover:text-white transition font-bold flex items-center justify-center gap-2">
169
169
  <i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali ke Beranda
170
170
  </a>
@@ -2,7 +2,7 @@
2
2
  <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
3
3
  <div>
4
4
  <span class="inline-block bg-indigo-500/20 text-indigo-200 text-xs font-bold px-3 py-1 rounded-full mb-3 border border-indigo-500/30">
5
- v2.3.2 Alpha
5
+ v3.3.3 Alpha
6
6
  </span>
7
7
  <h1 class="text-4xl font-extrabold tracking-tight">
8
8
  Halo, <span class="text-indigo-300"><%= @nama %></span>!
@@ -10,6 +10,38 @@
10
10
  <meta property="og:title" content="Eksa Framework">
11
11
  <meta property="og:description" content="Membangun web cepat dengan estetika transparan.">
12
12
  <meta property="og:type" content="website">
13
+
14
+ <%
15
+ scheme = request.env['rack.url_scheme'] || 'https'
16
+ base_url = "#{scheme}://#{request.host}"
17
+ current_url = "#{base_url}#{request.path}"
18
+
19
+ if @post
20
+ structured_data = {
21
+ "@context": "https://schema.org",
22
+ "@type": "BlogPosting",
23
+ "headline": @post.title,
24
+ "datePublished": (@post.date.is_a?(Time) ? @post.date.iso8601 : @post.date),
25
+ "author": {
26
+ "@type": "Person",
27
+ "name": (@post.author && !@post.author.empty?) ? @post.author : 'IshikawaUta'
28
+ },
29
+ "url": current_url,
30
+ "description": @post.body_html[0..160].gsub(/<[^>]*>/, '').strip + "..."
31
+ }
32
+ else
33
+ structured_data = {
34
+ "@context": "https://schema.org",
35
+ "@type": "WebSite",
36
+ "name": "Eksa Framework",
37
+ "url": base_url,
38
+ "description": "Framework Ruby Modern dengan sentuhan Glassmorphism"
39
+ }
40
+ end
41
+ %>
42
+ <script type="application/ld+json">
43
+ <%= structured_data.to_json %>
44
+ </script>
13
45
  <link rel="preconnect" href="https://fonts.googleapis.com">
14
46
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
47
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
@@ -19,14 +19,32 @@
19
19
  </div>
20
20
  <% else %>
21
21
  <% @posts.each do |post| %>
22
- <a href="/posts/<%= post.slug %>" class="group block bg-white/5 hover:bg-white/10 p-6 rounded-3xl border border-white/10 transition-all hover:scale-[1.02]">
23
- <span class="text-xs font-bold text-indigo-300 uppercase tracking-widest block mb-2">
24
- <%= post.date.is_a?(Time) ? post.date.strftime("%d %B %Y") : post.date %>
25
- </span>
26
- <h2 class="text-2xl font-bold mb-3 group-hover:text-indigo-300 transition-colors"><%= post.title %></h2>
27
- <div class="flex items-center gap-2 text-white/40 text-sm">
28
- <span>Baca selengkapnya</span>
29
- <i data-lucide="arrow-right" class="w-4 h-4 group-hover:translate-x-1 transition-transform"></i>
22
+ <a href="/posts/<%= post.slug %>" class="group block bg-white/5 hover:bg-white/10 overflow-hidden rounded-3xl border border-white/10 transition-all hover:scale-[1.02]">
23
+ <% if !post.image.empty? %>
24
+ <div class="h-48 overflow-hidden">
25
+ <img src="<%= post.image %>" alt="<%= post.title %>" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
26
+ </div>
27
+ <% end %>
28
+ <div class="p-6">
29
+ <div class="flex items-center justify-between mb-4">
30
+ <span class="text-[10px] font-bold text-indigo-300 uppercase tracking-widest px-2 py-1 bg-indigo-500/20 rounded-md border border-indigo-500/30">
31
+ <%= post.category %>
32
+ </span>
33
+ <span class="text-[10px] font-medium text-white/40 uppercase tracking-widest">
34
+ <%= post.date.is_a?(Time) ? post.date.strftime("%d %B %Y") : post.date %>
35
+ </span>
36
+ </div>
37
+ <h2 class="text-2xl font-bold mb-3 group-hover:text-indigo-300 transition-colors"><%= post.title %></h2>
38
+ <div class="flex items-center justify-between mt-6 pt-4 border-t border-white/5">
39
+ <div class="flex items-center gap-2 text-white/60 text-xs">
40
+ <i data-lucide="user" class="w-3 h-3"></i>
41
+ <span><%= post.author.empty? ? "Eksa Dev" : post.author %></span>
42
+ </div>
43
+ <div class="flex items-center gap-1 text-indigo-300 text-xs font-bold">
44
+ <span>Baca</span>
45
+ <i data-lucide="arrow-right" class="w-3 h-3 group-hover:translate-x-1 transition-transform"></i>
46
+ </div>
47
+ </div>
30
48
  </div>
31
49
  </a>
32
50
  <% end %>
@@ -3,11 +3,31 @@
3
3
  <a href="/posts" class="inline-flex items-center gap-2 text-indigo-300 hover:text-white transition mb-6">
4
4
  <i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali ke Blog
5
5
  </a>
6
- <span class="text-xs font-bold text-indigo-300 uppercase tracking-widest block mb-2">
7
- <%= @post.date.is_a?(Time) ? @post.date.strftime("%d %B %Y") : @post.date %>
8
- </span>
9
- <h3 class="text-5xl font-extrabold tracking-tight mb-4"><%= @post.title %></h3>
10
- <div class="h-1 w-20 bg-indigo-500 rounded-full"></div>
6
+ <div class="mb-4 flex items-center gap-3">
7
+ <span class="px-3 py-1 bg-indigo-500/20 rounded-lg border border-indigo-500/30 text-xs font-bold text-indigo-300 uppercase tracking-widest">
8
+ <%= @post.category %>
9
+ </span>
10
+ <span class="text-xs font-medium text-white/40 uppercase tracking-widest">
11
+ <%= @post.date.is_a?(Time) ? @post.date.strftime("%d %B %Y") : @post.date %>
12
+ </span>
13
+ </div>
14
+ <h3 class="text-5xl font-extrabold tracking-tight mb-6 leading-tight"><%= @post.title %></h3>
15
+
16
+ <div class="flex items-center gap-4 mb-8 pb-8 border-b border-white/10">
17
+ <div class="w-10 h-10 rounded-full bg-indigo-500 flex items-center justify-center text-white shadow-lg shadow-indigo-500/30">
18
+ <i data-lucide="user" class="w-5 h-5"></i>
19
+ </div>
20
+ <div>
21
+ <p class="text-sm font-bold text-white"><%= @post.author.empty? ? "Eksa Dev" : @post.author %></p>
22
+ <p class="text-[10px] text-white/40 uppercase tracking-widest">Penulis Artikel</p>
23
+ </div>
24
+ </div>
25
+
26
+ <% if !@post.image.empty? %>
27
+ <div class="mb-10 rounded-3xl overflow-hidden shadow-2xl border border-white/10">
28
+ <img src="<%= @post.image %>" alt="<%= @post.title %>" class="w-full object-cover max-h-[400px]">
29
+ </div>
30
+ <% end %>
11
31
  </div>
12
32
 
13
33
  <div class="prose prose-invert prose-indigo max-w-none leading-relaxed text-white/80">
data/exe/eksa CHANGED
@@ -10,7 +10,7 @@ def usage
10
10
  puts " eksa init - Inisialisasi project baru"
11
11
  puts " eksa g controller NAME - Generate controller baru"
12
12
  puts " eksa g model NAME - Generate model baru"
13
- puts " eksa g post TITLE - Generate blog post markdown baru"
13
+ puts " eksa g post TITLE - Post baru [args: --category, --author, --image]"
14
14
  puts " eksa run - Jalankan server aplikasi"
15
15
  puts "-----------------------------\n"
16
16
  end
@@ -115,16 +115,23 @@ def generate_model(name)
115
115
  puts " [OK] Created #{file_path}"
116
116
  end
117
117
 
118
- def generate_post(title)
118
+ def generate_post(title, options = {})
119
119
  FileUtils.mkdir_p("_posts")
120
120
  date = Time.now.strftime("%Y-%m-%d")
121
121
  slug = title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
122
122
  filename = "_posts/#{date}-#{slug}.md"
123
123
 
124
+ author = options[:author] || ""
125
+ category = options[:category] || "Uncategorized"
126
+ image = options[:image] || ""
127
+
124
128
  content = <<~MARKDOWN
125
129
  ---
126
130
  title: "#{title}"
127
131
  date: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}
132
+ author: "#{author}"
133
+ category: "#{category}"
134
+ image: "#{image}"
128
135
  ---
129
136
 
130
137
  Tulis konten blog Anda di sini...
@@ -160,7 +167,15 @@ when 'g', 'generate'
160
167
  elsif subcommand == 'model' && name
161
168
  generate_model(name)
162
169
  elsif subcommand == 'post' && name
163
- generate_post(name)
170
+ options = {}
171
+ while (arg = ARGV.shift)
172
+ case arg
173
+ when '--category' then options[:category] = ARGV.shift
174
+ when '--author' then options[:author] = ARGV.shift
175
+ when '--image' then options[:image] = ARGV.shift
176
+ end
177
+ end
178
+ generate_post(name, options)
164
179
  else
165
180
  usage
166
181
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
1
4
  require 'yaml'
2
5
  require 'kramdown'
3
6
 
@@ -33,6 +36,18 @@ module Eksa
33
36
  @metadata['date'] || File.mtime(File.join(POSTS_DIR, @filename))
34
37
  end
35
38
 
39
+ def author
40
+ @metadata['author'] || ""
41
+ end
42
+
43
+ def category
44
+ @metadata['category'] || "Uncategorized"
45
+ end
46
+
47
+ def image
48
+ @metadata['image'] || ""
49
+ end
50
+
36
51
  def body_html
37
52
  Kramdown::Document.new(@content, input: 'GFM').to_html
38
53
  end
@@ -40,7 +55,7 @@ module Eksa
40
55
  private
41
56
 
42
57
  def load_file(file_path)
43
- content = File.read(file_path)
58
+ content = File.read(file_path, encoding: 'utf-8')
44
59
  # Improved regex: handle optional trailing newline after second separator
45
60
  if content =~ /\A(---\s*\r?\n.*?\r?\n)^(---\s*\r?\n?)/m
46
61
  @metadata = YAML.safe_load($1, permitted_classes: [Time])
data/lib/eksa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eksa
2
- VERSION = "2.3.2"
2
+ VERSION = "3.3.3"
3
3
  end
data/lib/eksa.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rack'
2
+ require 'json'
2
3
  require_relative 'eksa/version'
3
4
  require_relative 'eksa/controller'
4
5
  require_relative 'eksa/model'
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eksa-framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.2
4
+ version: 3.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - IshikawaUta
@@ -129,6 +129,7 @@ files:
129
129
  - lib/eksa/version.rb
130
130
  - public/css/style.css
131
131
  - public/img/favicon.ico
132
+ - public/img/logo.jpg
132
133
  - public/img/logo.png
133
134
  - spec/application_spec.rb
134
135
  - spec/markdown_post_spec.rb