shiplog 0.2.0 → 0.3.1
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 +74 -0
- data/README.md +172 -7
- data/app/controllers/shiplog/admin/area_detections_controller.rb +15 -0
- data/app/controllers/shiplog/admin/area_translations_controller.rb +19 -0
- data/app/controllers/shiplog/admin/areas/merges_controller.rb +19 -0
- data/app/controllers/shiplog/admin/areas/moves_controller.rb +13 -0
- data/app/controllers/shiplog/admin/areas_controller.rb +48 -0
- data/app/controllers/shiplog/admin/entries/images/moves_controller.rb +20 -0
- data/app/controllers/shiplog/admin/entries/images_controller.rb +76 -0
- data/app/controllers/shiplog/admin/entries/translations/retranslations_controller.rb +23 -0
- data/app/controllers/shiplog/admin/entries/translations_controller.rb +35 -0
- data/app/controllers/shiplog/admin/entries_controller.rb +9 -3
- data/app/controllers/shiplog/changelog_entries_controller.rb +26 -0
- data/app/controllers/shiplog/changelog_feeds_controller.rb +19 -0
- data/app/controllers/shiplog/changelogs_controller.rb +21 -5
- data/app/controllers/shiplog/public_controller.rb +57 -0
- data/app/controllers/shiplog/roadmaps_controller.rb +3 -1
- data/app/frontend/shiplog/admin.css +9 -0
- data/app/frontend/shiplog/shiplog.css +47 -2
- data/app/helpers/shiplog/admin/entries_helper.rb +15 -0
- data/app/helpers/shiplog/head_helper.rb +95 -0
- data/app/helpers/shiplog/head_tags_helper.rb +24 -0
- data/app/helpers/shiplog/permalink_helper.rb +149 -0
- data/app/helpers/shiplog/roadmap_helper.rb +9 -0
- data/app/helpers/shiplog/structured_data_helper.rb +74 -0
- data/app/helpers/shiplog/unseen_helper.rb +9 -0
- data/app/jobs/shiplog/detect_areas_job.rb +18 -0
- data/app/jobs/shiplog/rewrite_entry_job.rb +4 -1
- data/app/jobs/shiplog/translate_area_job.rb +25 -0
- data/app/jobs/shiplog/translate_entry_job.rb +25 -0
- data/app/models/shiplog/area.rb +156 -0
- data/app/models/shiplog/area_detector.rb +146 -0
- data/app/models/shiplog/area_translator.rb +77 -0
- data/app/models/shiplog/entry/images.rb +127 -0
- data/app/models/shiplog/entry.rb +204 -5
- data/app/models/shiplog/entry_translation.rb +15 -0
- data/app/models/shiplog/image_fetcher/downloader.rb +98 -0
- data/app/models/shiplog/image_fetcher/sources.rb +41 -0
- data/app/models/shiplog/image_fetcher.rb +72 -0
- data/app/models/shiplog/image_type.rb +111 -0
- data/app/models/shiplog/rewriter/prompt.rb +31 -7
- data/app/models/shiplog/rewriter.rb +41 -23
- data/app/models/shiplog/setting.rb +19 -0
- data/app/models/shiplog/translator/prompt.rb +40 -0
- data/app/models/shiplog/translator.rb +99 -0
- data/app/views/layouts/shiplog/application.html.erb +1 -1
- data/app/views/shiplog/_empty.html.erb +1 -1
- data/app/views/shiplog/_head_tags.html.erb +5 -0
- data/app/views/shiplog/_pagination.html.erb +3 -3
- data/app/views/shiplog/admin/_nav.html.erb +2 -1
- data/app/views/shiplog/admin/areas/index.html.erb +78 -0
- data/app/views/shiplog/admin/entries/_entry_row.html.erb +3 -0
- data/app/views/shiplog/admin/entries/_images.html.erb +41 -0
- data/app/views/shiplog/admin/entries/_translations.html.erb +42 -0
- data/app/views/shiplog/admin/entries/edit.html.erb +12 -0
- data/app/views/shiplog/changelog_entries/show.html.erb +23 -0
- data/app/views/shiplog/changelog_feeds/show.atom.builder +24 -0
- data/app/views/shiplog/changelogs/_areas.html.erb +8 -0
- data/app/views/shiplog/changelogs/_entry.html.erb +2 -5
- data/app/views/shiplog/changelogs/_entry_content.html.erb +10 -0
- data/app/views/shiplog/changelogs/_search.html.erb +4 -2
- data/app/views/shiplog/changelogs/show.html.erb +13 -7
- data/app/views/shiplog/head/_feed.html.erb +1 -0
- data/app/views/shiplog/head/_meta.html.erb +3 -0
- data/app/views/shiplog/head/_open_graph.html.erb +6 -0
- data/app/views/shiplog/head/_structured_data.html.erb +1 -0
- data/app/views/shiplog/roadmaps/_card.html.erb +2 -2
- data/app/views/shiplog/roadmaps/_column.html.erb +3 -3
- data/app/views/shiplog/roadmaps/_shipped.html.erb +1 -1
- data/app/views/shiplog/roadmaps/show.html.erb +5 -2
- data/config/admin_routes.rb +16 -0
- data/config/locales/en.yml +85 -0
- data/config/locales/es.yml +223 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20261027000002_shiplog_0_3_0.rb +105 -0
- data/lib/generators/shiplog/install/install_generator.rb +10 -1
- data/lib/generators/shiplog/install/templates/initializer.rb.tt +58 -1
- data/lib/shiplog/classes.rb +10 -1
- data/lib/shiplog/configuration.rb +105 -3
- data/lib/shiplog/engine.rb +8 -0
- data/lib/shiplog/github/queries.rb +13 -1
- data/lib/shiplog/version.rb +1 -1
- data/lib/shiplog.rb +73 -0
- data/lib/tasks/shiplog.rake +29 -0
- metadata +47 -1
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<td>
|
|
3
3
|
<%= link_to entry_heading(entry), edit_entry_path(entry, inbox_filter_params) %>
|
|
4
4
|
<%= render "shiplog/admin/entries/badges", entry: %>
|
|
5
|
+
<% if entry.area %>
|
|
6
|
+
<span class="shiplog-badge shiplog-badge--area"><%= entry.area.name %></span>
|
|
7
|
+
<% end %>
|
|
5
8
|
<% if entry.discarded? && entry.discard_reason.present? %>
|
|
6
9
|
<div class="shiplog-admin__muted"><%= entry.discard_reason %></div>
|
|
7
10
|
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<%# locals: (entry:) %>
|
|
2
|
+
<section class="shiplog-admin-panel shiplog-admin-images">
|
|
3
|
+
<h2><%= t("shiplog.admin.entries.images.title", default: "Screenshots") %></h2>
|
|
4
|
+
<p class="shiplog-admin__muted"><%= t("shiplog.admin.entries.images.help", default: "Shown under the text on the changelog (the first one in the list, all of them on the entry's page). Taken from the GitHub issue or pull request when the draft is written; you can also upload your own.") %></p>
|
|
5
|
+
<% images = entry.ordered_images %>
|
|
6
|
+
<% if images.empty? %>
|
|
7
|
+
<p class="shiplog-admin__muted"><%= t("shiplog.admin.entries.images.empty", default: "No screenshots yet.") %></p>
|
|
8
|
+
<% else %>
|
|
9
|
+
<ol class="shiplog-admin-images__list">
|
|
10
|
+
<% images.each_with_index do |image, index| %>
|
|
11
|
+
<li class="shiplog-admin-images__item">
|
|
12
|
+
<% thumbnail = image_thumbnail_path(image) %>
|
|
13
|
+
<%= tag.img(src: thumbnail, alt: entry.image_alt(image), class: "shiplog-admin-images__thumb", loading: "lazy") if thumbnail %>
|
|
14
|
+
<div class="shiplog-admin-images__controls">
|
|
15
|
+
<%= form_with url: entry_image_path(entry, image.blob_id, inbox_filter_params), method: :patch, class: "shiplog-admin-inline-form" do |form| %>
|
|
16
|
+
<%= form.label :alt, t("shiplog.admin.entries.images.alt", default: "Alt text"), for: "alt_#{image.blob_id}" %>
|
|
17
|
+
<%= form.text_field :alt, id: "alt_#{image.blob_id}", value: entry.custom_image_alt(image), maxlength: Shiplog::Entry::Images::ALT_MAX_LENGTH, placeholder: t("shiplog.admin.entries.images.alt_placeholder", default: "Describe the image (defaults to the title)") %>
|
|
18
|
+
<%= form.submit t("shiplog.admin.entries.images.save_alt", default: "Save alt text"), class: "shiplog-button" %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<div class="shiplog-admin-inline-form">
|
|
21
|
+
<% if index.positive? %>
|
|
22
|
+
<%= button_to t("shiplog.admin.entries.images.move_up", default: "Up"), entry_image_move_path(entry, image.blob_id, inbox_filter_params.merge(direction: "up")), class: "shiplog-button" %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% if index < images.size - 1 %>
|
|
25
|
+
<%= button_to t("shiplog.admin.entries.images.move_down", default: "Down"), entry_image_move_path(entry, image.blob_id, inbox_filter_params.merge(direction: "down")), class: "shiplog-button" %>
|
|
26
|
+
<% end %>
|
|
27
|
+
<%= button_to t("shiplog.admin.entries.images.remove", default: "Remove"), entry_image_path(entry, image.blob_id, inbox_filter_params), method: :delete, class: "shiplog-button" %>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</li>
|
|
31
|
+
<% end %>
|
|
32
|
+
</ol>
|
|
33
|
+
<% end %>
|
|
34
|
+
<%= form_with url: entry_images_path(entry, inbox_filter_params), multipart: true, class: "shiplog-admin-form" do |form| %>
|
|
35
|
+
<p>
|
|
36
|
+
<%= form.label :images, t("shiplog.admin.entries.images.upload_label", default: "Images (PNG, JPEG, GIF or WebP, up to 5 MB each)") %>
|
|
37
|
+
<%= form.file_field :images, multiple: true, name: "images[]", accept: Shiplog::ImageType::EXTENSIONS.keys.join(",") %>
|
|
38
|
+
</p>
|
|
39
|
+
<%= form.submit t("shiplog.admin.entries.images.upload", default: "Upload images"), class: "shiplog-button" %>
|
|
40
|
+
<% end %>
|
|
41
|
+
</section>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<%# locals: (entry:) %>
|
|
2
|
+
<%# one editable title and body per configured locale (config.locales), with its state: missing, stale (made from older English text), edited by an admin, or up to date. Needs Shiplog::Admin::EntriesHelper. %>
|
|
3
|
+
<% translations = entry.translations.index_by(&:locale) %>
|
|
4
|
+
<section class="shiplog-admin-panel shiplog-translations">
|
|
5
|
+
<h2><%= t("shiplog.admin.entries.translations.title", default: "Translations") %></h2>
|
|
6
|
+
<p class="shiplog-admin__muted"><%= t("shiplog.admin.entries.translations.help", default: "Approving an entry translates it automatically, and the translation is published with the English text. A translation you edit is kept until the English text changes; then it is translated again. A stale or missing translation is not shown: visitors see the English text.") %></p>
|
|
7
|
+
<% unless entry.approved? %>
|
|
8
|
+
<p class="shiplog-admin__muted"><%= t("shiplog.admin.entries.translations.not_approved", default: "Translations are written when the entry is approved.") %></p>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% Shiplog.config.locales.each do |locale| %>
|
|
11
|
+
<% translation = translations[locale.to_s] %>
|
|
12
|
+
<div class="shiplog-translation" data-locale="<%= locale %>">
|
|
13
|
+
<h3>
|
|
14
|
+
<%= translation_language_label(locale) %>
|
|
15
|
+
<% if translation.nil? %>
|
|
16
|
+
<span class="shiplog-badge"><%= t("shiplog.admin.entries.translations.missing", default: "Missing") %></span>
|
|
17
|
+
<% elsif translation.stale? %>
|
|
18
|
+
<span class="shiplog-badge"><%= t("shiplog.admin.entries.translations.stale", default: "Stale") %></span>
|
|
19
|
+
<% else %>
|
|
20
|
+
<span class="shiplog-badge"><%= t("shiplog.admin.entries.translations.current", default: "Up to date") %></span>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% if translation&.edited_by_admin %>
|
|
23
|
+
<span class="shiplog-badge"><%= t("shiplog.admin.entries.translations.edited", default: "Edited by an admin") %></span>
|
|
24
|
+
<% end %>
|
|
25
|
+
</h3>
|
|
26
|
+
<%= form_with url: entry_translation_path(entry, inbox_filter_params.merge(translation_locale: locale)), scope: :translation, method: :patch, class: "shiplog-admin-form" do |form| %>
|
|
27
|
+
<p>
|
|
28
|
+
<%= form.label :title, t("shiplog.admin.entries.fields.title"), for: "translation_#{locale}_title" %>
|
|
29
|
+
<%= form.text_field :title, value: translation&.title, id: "translation_#{locale}_title", lang: locale %>
|
|
30
|
+
</p>
|
|
31
|
+
<p>
|
|
32
|
+
<%= form.label :body, t("shiplog.admin.entries.fields.body"), for: "translation_#{locale}_body" %>
|
|
33
|
+
<%= form.text_area :body, value: translation&.body, rows: 5, id: "translation_#{locale}_body", lang: locale %>
|
|
34
|
+
</p>
|
|
35
|
+
<%= form.submit t("shiplog.admin.entries.save"), class: "shiplog-button shiplog-button--primary" %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% if entry.approved? %>
|
|
38
|
+
<%= button_to t("shiplog.admin.entries.translations.retranslate", default: "Retranslate"), entry_translation_retranslation_path(entry, inbox_filter_params.merge(translation_locale: locale)), class: "shiplog-button" %>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</section>
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
<%= form.select :changelog_kind, Shiplog::Entry::CHANGELOG_KINDS.map { |kind| [ t("shiplog.admin.entries.category_options.#{kind}"), kind ] } %>
|
|
29
29
|
</p>
|
|
30
30
|
<% end %>
|
|
31
|
+
<p>
|
|
32
|
+
<%= form.label :area_id, t("shiplog.admin.entries.fields.area", default: "Area") %>
|
|
33
|
+
<%= form.collection_select :area_id, Shiplog::Area.ordered, :id, :name, include_blank: t("shiplog.admin.entries.no_area", default: "None") %>
|
|
34
|
+
</p>
|
|
31
35
|
<p>
|
|
32
36
|
<%= form.label :released_on, t("shiplog.admin.entries.fields.released_on") %>
|
|
33
37
|
<%= form.date_field :released_on %>
|
|
@@ -63,5 +67,13 @@
|
|
|
63
67
|
</aside>
|
|
64
68
|
</div>
|
|
65
69
|
|
|
70
|
+
<% if Shiplog.config.locales.any? %>
|
|
71
|
+
<%= render "shiplog/admin/entries/translations", entry: @entry %>
|
|
72
|
+
<% end %>
|
|
73
|
+
|
|
74
|
+
<% if Shiplog.images? %>
|
|
75
|
+
<%= render "shiplog/admin/entries/images", entry: @entry %>
|
|
76
|
+
<% end %>
|
|
77
|
+
|
|
66
78
|
<p><%= link_to t("shiplog.admin.entries.back"), entries_filter_path(**inbox_filter_params) %></p>
|
|
67
79
|
<% end %>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<% content_for :title, t("shiplog.permalink.title", title: @entry.display_title, changelog: t("shiplog.changelog.title", default: "Changelog"), default: "%{title} · %{changelog}") %>
|
|
2
|
+
<% shiplog_head_page :permalink, title: @entry.display_title, entry: @entry, url: shiplog_entry_url(@entry), json_ld: shiplog_article_json_ld(@entry) %>
|
|
3
|
+
<%= render layout: "shiplog/page", locals: { name: "permalink" } do %>
|
|
4
|
+
<%= render "shiplog/page_header", title: t("shiplog.changelog.title", default: "Changelog"), lede: nil %>
|
|
5
|
+
<article class="<%= shiplog_class(:permalink, @entry.kind) %>">
|
|
6
|
+
<%= render "shiplog/changelogs/entry_content", entry: @entry, heading: "h2" %>
|
|
7
|
+
</article>
|
|
8
|
+
<%= link_to t("shiplog.permalink.back", default: "← Back to the changelog"), changelog_path(anchor: shiplog_month_anchor(@entry.released_on)), class: shiplog_class(:permalink_back) %>
|
|
9
|
+
<% if @newer || @older %>
|
|
10
|
+
<nav class="<%= shiplog_class(:permalink_nav) %>" aria-label="<%= t("shiplog.permalink.nav_label", default: "More changelog entries") %>">
|
|
11
|
+
<% if @newer %>
|
|
12
|
+
<%= shiplog_entry_link @newer, class: shiplog_class(:permalink_nav_link, :newer), rel: "prev" do %>
|
|
13
|
+
<%= t("shiplog.permalink.newer", title: @newer.display_title, default: "← Newer: %{title}") %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if @older %>
|
|
17
|
+
<%= shiplog_entry_link @older, class: shiplog_class(:permalink_nav_link, :older), rel: "next" do %>
|
|
18
|
+
<%= t("shiplog.permalink.older", title: @older.display_title, default: "Older: %{title} →") %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</nav>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# The latest published features and changes. Entry ids are tag URIs built from the permalink path, so they
|
|
2
|
+
# stay the same when a title changes.
|
|
3
|
+
title = Shiplog.config.product_name.present? ? t("shiplog.feed.title_with_product", product: Shiplog.config.product_name, default: "%{product} changelog") : t("shiplog.changelog.title", default: "Changelog")
|
|
4
|
+
|
|
5
|
+
atom_feed(language: I18n.locale.to_s, url: shiplog_absolute_url(:changelog_feed), root_url: shiplog_absolute_url(:changelog),
|
|
6
|
+
id: "tag:#{shiplog_tag_host},2005:#{changelog_path}") do |feed|
|
|
7
|
+
feed.title(title)
|
|
8
|
+
feed.author { |author| author.name(Shiplog.config.product_name.presence || "Changelog") } # RFC 4287 4.1.1: the feed or each entry needs one
|
|
9
|
+
# An entry is updated when its text or the translation shown in this feed changes.
|
|
10
|
+
updated_at = ->(entry) { [ entry.updated_at, entry.translation&.updated_at ].compact.max }
|
|
11
|
+
feed.updated(@entries.map(&updated_at).max || Time.current)
|
|
12
|
+
|
|
13
|
+
# Under a translated locale, an entry without a current translation is in English (the default locale): say so.
|
|
14
|
+
english = ->(entry) { Shiplog::Entry.translated_locale?(I18n.locale) && entry.translation.nil? ? { "xml:lang" => I18n.default_locale.to_s } : {} }
|
|
15
|
+
|
|
16
|
+
@entries.each do |entry|
|
|
17
|
+
feed.entry(entry, id: "tag:#{shiplog_tag_host},2005:#{shiplog_entry_path(entry)}", url: shiplog_entry_url(entry),
|
|
18
|
+
published: entry.released_on.in_time_zone, updated: updated_at.(entry)) do |item|
|
|
19
|
+
item.title(entry.display_title.to_s, english.(entry))
|
|
20
|
+
item.category(term: entry.area.slug, label: entry.area.display_name) if entry.area
|
|
21
|
+
item.content(shiplog_feed_content(entry), { type: "html" }.merge(english.(entry)))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%# locals: (areas:, current: nil, query: nil) %>
|
|
2
|
+
<%# the area filter: an "All" link and one link per area (GET ?area=slug, works without JavaScript); the search query is kept %>
|
|
3
|
+
<nav class="<%= shiplog_class(:areas) %>" aria-label="<%= t("shiplog.changelog.areas.label", default: "Filter by area") %>">
|
|
4
|
+
<%= link_to t("shiplog.changelog.areas.all", default: "All"), changelog_path(q: query), class: shiplog_class(:area_link, ("current" unless current)), "aria-current": ("page" unless current) %>
|
|
5
|
+
<% areas.each do |area| %>
|
|
6
|
+
<%= link_to area.display_name, changelog_path(area: area.slug, q: query), class: shiplog_class(:area_link, ("current" if area == current)), "aria-current": ("page" if area == current) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
</nav>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<%# locals: (entry:) %>
|
|
2
|
-
<%# one changelog entry
|
|
2
|
+
<%# one changelog entry in the list; the entry itself is the entry_content partial, shared with the permalink page %>
|
|
3
3
|
<li class="<%= token_list(shiplog_class(:entry, entry.kind), (shiplog_class(:entry_feature) if entry.feature?)) %>">
|
|
4
|
-
|
|
5
|
-
<time class="<%= shiplog_class(:entry_date) %>" datetime="<%= entry.released_on.iso8601 %>"><%= l(entry.released_on, format: t("shiplog.date_format", default: "%-d %B %Y")) %></time>
|
|
6
|
-
<h3 class="<%= shiplog_class(:entry_title) %>"><%= entry.title %></h3>
|
|
7
|
-
<div class="<%= shiplog_class(:entry_body) %>"><%= simple_format(h(entry.body)) %></div>
|
|
4
|
+
<%= render "shiplog/changelogs/entry_content", entry:, heading: "h3", linked: true %>
|
|
8
5
|
</li>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%# locals: (entry:, heading: "h3", linked: false, all_images: !linked) %>
|
|
2
|
+
<%# what an entry shows: category pill ("New" for features, "Improved" for changes), date, title (a permalink link in the list) and body. The area tag (features and changes only) follows the category. The images follow the body: the first one in the list, all of them on the permalink page. %>
|
|
3
|
+
<span class="<%= shiplog_class(:entry_category) %>"><%= t("shiplog.categories.#{entry.kind}", default: (entry.feature? ? "New" : "Improved")) %></span>
|
|
4
|
+
<% if entry.area && Shiplog::Entry::CHANGELOG_KINDS.include?(entry.kind) %>
|
|
5
|
+
<span class="<%= shiplog_class(:entry_area) %>"><%= entry.area.display_name %></span>
|
|
6
|
+
<% end %>
|
|
7
|
+
<time class="<%= shiplog_class(:entry_date) %>" datetime="<%= entry.released_on.iso8601 %>"><%= l(entry.released_on, format: t("shiplog.date_format", default: "%-d %B %Y")) %></time>
|
|
8
|
+
<%= content_tag heading, (linked && shiplog_entry_path(entry) ? link_to(entry.display_title, shiplog_entry_path(entry)) : entry.display_title), class: shiplog_class(:entry_title) %>
|
|
9
|
+
<div class="<%= shiplog_class(:entry_body) %>"><%= simple_format(h(entry.display_body)) %></div>
|
|
10
|
+
<%= shiplog_entry_images(entry, limit: (all_images ? nil : 1)) %>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<%# locals: (query: nil, count: nil) %>
|
|
2
|
-
<%# the
|
|
2
|
+
<%# the selected area is read from @area, not a local: a copy of this partial from 0.2.0 declares only query and count %>
|
|
3
|
+
<%# the changelog search form (GET, works without JavaScript); with a query, also the result summary and a link back to the changelog (of the same area, when one is selected) %>
|
|
3
4
|
<% label = t("shiplog.changelog.search.label", default: "Search the changelog") %>
|
|
4
5
|
<form class="<%= shiplog_class(:search) %>" role="search" action="<%= changelog_path %>" method="get">
|
|
5
6
|
<input class="<%= shiplog_class(:search_input) %>" type="search" name="q" value="<%= query %>" aria-label="<%= label %>" placeholder="<%= label %>" maxlength="<%= Shiplog::Entry::SEARCH_MAX_LENGTH %>">
|
|
7
|
+
<% if @area %><input type="hidden" name="area" value="<%= @area.slug %>"><% end %>
|
|
6
8
|
<button class="<%= shiplog_class(:search_button) %>" type="submit"><%= t("shiplog.changelog.search.submit", default: "Search") %></button>
|
|
7
9
|
</form>
|
|
8
10
|
<% if query %>
|
|
@@ -11,6 +13,6 @@
|
|
|
11
13
|
<% if count.to_i.positive? %>
|
|
12
14
|
<%= t("shiplog.changelog.search.summary", count: count, query: query, default: { one: "%{count} result for “%{query}”", other: "%{count} results for “%{query}”" }) %>
|
|
13
15
|
<% end %>
|
|
14
|
-
<%= link_to t("shiplog.changelog.search.clear", default: "Clear search"), changelog_path, class: shiplog_class(:search_clear) %>
|
|
16
|
+
<%= link_to t("shiplog.changelog.search.clear", default: "Clear search"), changelog_path(area: @area&.slug), class: shiplog_class(:search_clear) %>
|
|
15
17
|
</p>
|
|
16
18
|
<% end %>
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
<%
|
|
1
|
+
<% changelog_title = t("shiplog.changelog.title", default: "Changelog") %>
|
|
2
|
+
<% list_url = shiplog_absolute_url(:changelog, area: @area&.slug, page: (@page if @page > 1)) %>
|
|
3
|
+
<% content_for :title, changelog_title %>
|
|
4
|
+
<% shiplog_head_page :changelog, title: changelog_title, url: list_url, query: @query, json_ld: shiplog_changelog_json_ld(@months.values.flatten, name: changelog_title, url: list_url) %>
|
|
2
5
|
<%= render layout: "shiplog/page", locals: { name: "changelog" } do %>
|
|
3
|
-
<%= render "shiplog/page_header", title: t("shiplog.changelog.title"), lede: t("shiplog.changelog.lede") %>
|
|
4
|
-
<% if @query %>
|
|
5
|
-
<% content_for :shiplog_robots, "noindex" %>
|
|
6
|
-
<% end %>
|
|
6
|
+
<%= render "shiplog/page_header", title: t("shiplog.changelog.title", default: "Changelog"), lede: t("shiplog.changelog.lede", default: "New features and improvements, as they ship.") %>
|
|
7
7
|
<% if Shiplog.config.changelog_search %>
|
|
8
8
|
<%= render "shiplog/changelogs/search", query: @query, count: @result_count %>
|
|
9
9
|
<% end %>
|
|
10
|
+
<% if @areas.any? %>
|
|
11
|
+
<%= render "shiplog/changelogs/areas", areas: @areas, current: @area, query: @query %>
|
|
12
|
+
<% end %>
|
|
10
13
|
<% if @months.empty? && @query %>
|
|
11
14
|
<p class="<%= shiplog_class(:search_empty) %>"><%= t("shiplog.changelog.search.empty", query: @query, default: "No entries match “%{query}”.") %></p>
|
|
12
15
|
<% elsif @months.empty? %>
|
|
@@ -19,6 +22,9 @@
|
|
|
19
22
|
</div>
|
|
20
23
|
<% end %>
|
|
21
24
|
<%= render "shiplog/pagination",
|
|
22
|
-
newer_path: (changelog_path(page: (@page - 1 if @page > 2), q: @query) if @page > 1),
|
|
23
|
-
older_path: (changelog_path(page: @page + 1, q: @query) if @older) %>
|
|
25
|
+
newer_path: (changelog_path(area: @area&.slug, page: (@page - 1 if @page > 2), q: @query) if @page > 1),
|
|
26
|
+
older_path: (changelog_path(area: @area&.slug, page: @page + 1, q: @query) if @older) %>
|
|
27
|
+
<p class="<%= shiplog_class(:footnote) %>">
|
|
28
|
+
<%= link_to t("shiplog.feed.subscribe", default: "Subscribe (Atom)"), changelog_feed_path, class: shiplog_class(:footnote_link, :feed) %>
|
|
29
|
+
</p>
|
|
24
30
|
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= auto_discovery_link_tag(:atom, feed_url, title: feed_title) if feed_url.present? %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= json_ld if json_ld.present? %>
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
parts = issue.children.size
|
|
7
7
|
%>
|
|
8
8
|
<li class="<%= shiplog_class(:card) %>">
|
|
9
|
-
<h3 class="<%= shiplog_class(:card_title) %>"><%= entry.
|
|
10
|
-
<div class="<%= shiplog_class(:card_body) %>"><%= simple_format(h(entry.
|
|
9
|
+
<h3 class="<%= shiplog_class(:card_title) %>"><%= entry.display_title %></h3>
|
|
10
|
+
<div class="<%= shiplog_class(:card_body) %>"><%= simple_format(h(entry.display_body)) %></div>
|
|
11
11
|
<% if tag_label || parts > 1 %>
|
|
12
12
|
<p class="<%= shiplog_class(:card_meta) %>">
|
|
13
13
|
<% if tag_label %><span class="<%= shiplog_class(:card_tag) %>"><%= tag_label %></span><% end %>
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
<% heading_id = "shiplog-column-#{column.dasherize}" %>
|
|
4
4
|
<section class="<%= shiplog_class(:column, column) %>" aria-labelledby="<%= heading_id %>">
|
|
5
5
|
<header class="<%= shiplog_class(:column_header) %>">
|
|
6
|
-
<h2 class="<%= shiplog_class(:column_title) %>" id="<%= heading_id %>"><span class="<%= shiplog_class(:column_marker, column) %>" aria-hidden="true"></span><%= t("shiplog.columns.#{column}") %></h2>
|
|
6
|
+
<h2 class="<%= shiplog_class(:column_title) %>" id="<%= heading_id %>"><span class="<%= shiplog_class(:column_marker, column) %>" aria-hidden="true"></span><%= t("shiplog.columns.#{column}", default: shiplog_column_defaults.fetch(column.to_sym, {})[:title] || column.humanize) %></h2>
|
|
7
7
|
<span class="<%= shiplog_class(:column_count) %>"><%= entries.size %></span>
|
|
8
|
-
<p class="<%= shiplog_class(:column_description) %>"><%= t("shiplog.columns.#{column}_description") %></p>
|
|
8
|
+
<p class="<%= shiplog_class(:column_description) %>"><%= t("shiplog.columns.#{column}_description", default: shiplog_column_defaults.fetch(column.to_sym, {})[:description].to_s) %></p>
|
|
9
9
|
</header>
|
|
10
10
|
<% if entries.empty? %>
|
|
11
|
-
<p class="<%= shiplog_class(:column_empty) %>"><%= t("shiplog.empty") %></p>
|
|
11
|
+
<p class="<%= shiplog_class(:column_empty) %>"><%= t("shiplog.empty", default: "Nothing here yet") %></p>
|
|
12
12
|
<% else %>
|
|
13
13
|
<ol class="<%= shiplog_class(:cards) %>">
|
|
14
14
|
<%= render partial: "shiplog/roadmaps/card", collection: entries, as: :entry %>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<% entries.each do |entry| %>
|
|
9
9
|
<li class="<%= shiplog_class(:shipped_item) %>">
|
|
10
10
|
<time class="<%= shiplog_class(:shipped_date) %>" datetime="<%= entry.released_on.iso8601 %>"><%= l(entry.released_on, format: t("shiplog.roadmap.shipped_date_format", default: "%-d %B")) %></time>
|
|
11
|
-
<%= link_to entry.
|
|
11
|
+
<%= link_to entry.display_title, changelog_path(anchor: "shiplog-month-#{entry.released_on.strftime("%Y-%m")}"), class: shiplog_class(:shipped_link) %>
|
|
12
12
|
</li>
|
|
13
13
|
<% end %>
|
|
14
14
|
</ul>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
<%
|
|
1
|
+
<% roadmap_title = t("shiplog.roadmap.title", default: "Roadmap") %>
|
|
2
|
+
<% roadmap_url = shiplog_absolute_url(:roadmap) %>
|
|
3
|
+
<% content_for :title, roadmap_title %>
|
|
4
|
+
<% shiplog_head_page :roadmap, title: roadmap_title, url: roadmap_url, feed: false, json_ld: shiplog_roadmap_json_ld(@columns.values.flatten.map(&:entry), name: roadmap_title, url: roadmap_url) %>
|
|
2
5
|
<%= render layout: "shiplog/page", locals: { name: "roadmap" } do %>
|
|
3
|
-
<%= render "shiplog/page_header", title: t("shiplog.roadmap.title"), lede: t("shiplog.roadmap.lede") %>
|
|
6
|
+
<%= render "shiplog/page_header", title: t("shiplog.roadmap.title", default: "Roadmap"), lede: t("shiplog.roadmap.lede", default: "What we're working on and what comes next.") %>
|
|
4
7
|
<div class="<%= shiplog_class(:columns) %>">
|
|
5
8
|
<% @columns.each do |column, items| %>
|
|
6
9
|
<%= render "shiplog/roadmaps/column", column:, entries: items.map(&:entry) %>
|
data/config/admin_routes.rb
CHANGED
|
@@ -5,6 +5,22 @@ Shiplog::Admin::Engine.routes.draw do
|
|
|
5
5
|
resource :approval, only: :create
|
|
6
6
|
resource :discard, only: %i[create destroy]
|
|
7
7
|
resource :rewrite, only: :create
|
|
8
|
+
# The locale segment is :translation_locale, not :locale: a host's around_action that switches the locale
|
|
9
|
+
# from params[:locale] must not read it (a configured translation locale may not be an available one).
|
|
10
|
+
locale = { translation_locale: /[A-Za-z][A-Za-z0-9_-]*/ }
|
|
11
|
+
patch "translations/:translation_locale", to: "translations#update", as: :translation, constraints: locale
|
|
12
|
+
post "translations/:translation_locale/retranslation", to: "translations/retranslations#create", as: :translation_retranslation, constraints: locale
|
|
13
|
+
resources :images, only: %i[create update destroy] do
|
|
14
|
+
resource :move, only: :create, module: :images
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
resource :area_detection, only: :create, path: "areas/detection"
|
|
19
|
+
resource :area_translation, only: :create, path: "areas/translation"
|
|
20
|
+
resources :areas, only: %i[index update destroy] do
|
|
21
|
+
scope module: :areas do
|
|
22
|
+
resource :merge, only: :create
|
|
23
|
+
resource :move, only: :create
|
|
8
24
|
end
|
|
9
25
|
end
|
|
10
26
|
resource :board, only: %i[show update]
|
data/config/locales/en.yml
CHANGED
|
@@ -46,12 +46,61 @@ en:
|
|
|
46
46
|
other: "%{count} results for “%{query}”"
|
|
47
47
|
clear: Clear search
|
|
48
48
|
empty: "No entries match “%{query}”."
|
|
49
|
+
areas:
|
|
50
|
+
label: Filter by area
|
|
51
|
+
all: All
|
|
52
|
+
permalink:
|
|
53
|
+
title: "%{title} · %{changelog}"
|
|
54
|
+
back: "← Back to the changelog"
|
|
55
|
+
nav_label: More changelog entries
|
|
56
|
+
newer: "← Newer: %{title}"
|
|
57
|
+
older: "Older: %{title} →"
|
|
58
|
+
feed:
|
|
59
|
+
title_with_product: "%{product} changelog"
|
|
60
|
+
link_title: Changelog (Atom)
|
|
61
|
+
subscribe: Subscribe (Atom)
|
|
49
62
|
admin:
|
|
50
63
|
title: Shiplog admin
|
|
51
64
|
nav:
|
|
52
65
|
drafts: Drafts
|
|
53
66
|
board: Roadmap board
|
|
54
67
|
sync: Sync
|
|
68
|
+
areas: Areas
|
|
69
|
+
areas:
|
|
70
|
+
title: Product areas
|
|
71
|
+
help: The AI groups entries into areas (at most %{max}) while it rewrites them. Visitors can filter the changelog by area once two areas have published entries.
|
|
72
|
+
detect: Detect areas
|
|
73
|
+
recheck: Re-check unplaced entries
|
|
74
|
+
recheck_help: Ask the AI again about entries it left without an area, e.g. after renaming, merging or adding areas.
|
|
75
|
+
no_rewriter: Detection needs config.rewriter, which is not set.
|
|
76
|
+
last_detection: "Last detection: %{time}, assigned %{count}"
|
|
77
|
+
last_error: "Last detection failed (%{time}): %{error}"
|
|
78
|
+
detection_queued: Detection queued. Refresh in a moment.
|
|
79
|
+
translate_names: Translate names
|
|
80
|
+
translate_names_help: Ask the AI for the names that are missing in some language. Names that are already there, including ones you edited, are kept.
|
|
81
|
+
no_locales: Translating names needs config.locales, which is empty.
|
|
82
|
+
no_rewriter_translate: Translating names needs config.rewriter, which is not set.
|
|
83
|
+
translation_queued:
|
|
84
|
+
one: Translation queued for %{count} area. Refresh in a moment.
|
|
85
|
+
other: Translation queued for %{count} areas. Refresh in a moment.
|
|
86
|
+
empty: No areas yet. Detect areas to propose some from your entries.
|
|
87
|
+
columns:
|
|
88
|
+
name: Name
|
|
89
|
+
entries: Entries
|
|
90
|
+
order: Order
|
|
91
|
+
merge: Merge into
|
|
92
|
+
name_label: Name
|
|
93
|
+
rename: Rename
|
|
94
|
+
renamed: Area renamed.
|
|
95
|
+
move_up: Move up
|
|
96
|
+
move_down: Move down
|
|
97
|
+
merge: Merge
|
|
98
|
+
merge_into: Merge into
|
|
99
|
+
merged: "%{from} merged into %{into}."
|
|
100
|
+
merge_target: Choose another area to merge into.
|
|
101
|
+
merge_help: Merging moves the entries to the other area and deletes this one. Deleting an area leaves its entries without one.
|
|
102
|
+
delete: Delete
|
|
103
|
+
deleted: Area deleted. Its entries have no area now.
|
|
55
104
|
entries:
|
|
56
105
|
title: Entries
|
|
57
106
|
filters: Filter entries
|
|
@@ -73,6 +122,8 @@ en:
|
|
|
73
122
|
restore: Restore
|
|
74
123
|
save: Save
|
|
75
124
|
regenerate: Regenerate
|
|
125
|
+
no_area: None
|
|
126
|
+
area_gone: no longer exists
|
|
76
127
|
regenerate_warning: Regenerating unpublishes this entry until re-approved.
|
|
77
128
|
source_changed: GitHub text changed since this draft
|
|
78
129
|
rewrite_error: Rewrite failed
|
|
@@ -80,6 +131,39 @@ en:
|
|
|
80
131
|
public_text: Public text
|
|
81
132
|
source: GitHub source
|
|
82
133
|
back: Back to drafts
|
|
134
|
+
images:
|
|
135
|
+
title: Screenshots
|
|
136
|
+
help: Shown under the text on the changelog (the first one in the list, all of them on the entry's page). Taken from the GitHub issue or pull request when the draft is written; you can also upload your own.
|
|
137
|
+
empty: No screenshots yet.
|
|
138
|
+
alt: Alt text
|
|
139
|
+
alt_placeholder: Describe the image (defaults to the title)
|
|
140
|
+
save_alt: Save alt text
|
|
141
|
+
remove: Remove
|
|
142
|
+
move_up: Up
|
|
143
|
+
move_down: Down
|
|
144
|
+
upload: Upload images
|
|
145
|
+
upload_label: Images (PNG, JPEG, GIF or WebP, up to 5 MB each)
|
|
146
|
+
none_selected: Choose at least one image.
|
|
147
|
+
rejected: "Not added, only PNG, JPEG, GIF and WebP images up to 5 MB and 10,000 pixels a side are accepted: %{names}"
|
|
148
|
+
added:
|
|
149
|
+
one: Image added.
|
|
150
|
+
other: "%{count} images added."
|
|
151
|
+
alt_saved: Alt text saved.
|
|
152
|
+
removed: Image removed.
|
|
153
|
+
moved: Image moved.
|
|
154
|
+
translations:
|
|
155
|
+
title: Translations
|
|
156
|
+
help: "Approving an entry translates it automatically, and the translation is published with the English text. A translation you edit is kept until the English text changes; then it is translated again. A stale or missing translation is not shown: visitors see the English text."
|
|
157
|
+
not_approved: Translations are written when the entry is approved.
|
|
158
|
+
missing: Missing
|
|
159
|
+
stale: Stale
|
|
160
|
+
current: Up to date
|
|
161
|
+
edited: Edited by an admin
|
|
162
|
+
retranslate: Retranslate
|
|
163
|
+
saved: Translation saved.
|
|
164
|
+
queued: Translation queued. Refresh in a moment.
|
|
165
|
+
only_approved: Only approved entries are translated.
|
|
166
|
+
no_rewriter: Translating needs config.rewriter, which is not set.
|
|
83
167
|
category_options:
|
|
84
168
|
feature: New feature
|
|
85
169
|
change: Improvement
|
|
@@ -101,6 +185,7 @@ en:
|
|
|
101
185
|
body: Text
|
|
102
186
|
released_on: Released on
|
|
103
187
|
category: Category
|
|
188
|
+
area: Area
|
|
104
189
|
reason: Reason
|
|
105
190
|
board:
|
|
106
191
|
title: Roadmap board
|