shiplog 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -4
- data/README.md +178 -7
- data/app/controllers/shiplog/admin/announcements_controller.rb +19 -0
- data/app/controllers/shiplog/admin/entries/announcement_notifications_controller.rb +18 -0
- data/app/controllers/shiplog/admin/entries/exports_controller.rb +45 -0
- data/app/controllers/shiplog/admin/entries/newsletter_retries_controller.rb +17 -0
- data/app/controllers/shiplog/admin/entries/newsletter_tests_controller.rb +39 -0
- data/app/controllers/shiplog/admin/entries/newsletters_controller.rb +40 -0
- data/app/controllers/shiplog/admin/entries/translations_controller.rb +1 -0
- data/app/controllers/shiplog/admin/entries_controller.rb +7 -3
- data/app/controllers/shiplog/admin/newsletter_subscribers_controller.rb +47 -0
- data/app/controllers/shiplog/changelog_entries_controller.rb +14 -2
- data/app/controllers/shiplog/changelog_feeds_controller.rb +6 -2
- data/app/controllers/shiplog/changelogs_controller.rb +11 -1
- data/app/controllers/shiplog/frontends_controller.rb +2 -1
- data/app/controllers/shiplog/newsletter/confirmations_controller.rb +27 -0
- data/app/controllers/shiplog/newsletter/preferences_controller.rb +50 -0
- data/app/controllers/shiplog/newsletter/subscriptions_controller.rb +70 -0
- data/app/controllers/shiplog/newsletter/unsubscriptions_controller.rb +36 -0
- data/app/controllers/shiplog/newsletter_controller.rb +34 -0
- data/app/controllers/shiplog/public_controller.rb +10 -0
- data/app/frontend/shiplog/admin.css +5 -0
- data/app/frontend/shiplog/once.js +31 -0
- data/app/frontend/shiplog/shiplog.css +72 -0
- data/app/helpers/shiplog/admin/entries_helper.rb +2 -0
- data/app/helpers/shiplog/head_helper.rb +1 -1
- data/app/helpers/shiplog/newsletter_helper.rb +28 -0
- data/app/helpers/shiplog/newsletter_mailer_helper.rb +37 -0
- data/app/helpers/shiplog/permalink_helper.rb +24 -5
- data/app/helpers/shiplog/unseen_helper.rb +4 -0
- data/app/jobs/shiplog/announce_job.rb +30 -0
- data/app/jobs/shiplog/draft_announcement_job.rb +9 -0
- data/app/jobs/shiplog/purge_newsletter_job.rb +15 -0
- data/app/jobs/shiplog/send_newsletter_job.rb +64 -0
- data/app/jobs/shiplog/translate_entry_job.rb +2 -0
- data/app/mailers/shiplog/newsletter_mailer.rb +51 -0
- data/app/models/shiplog/announcements.rb +34 -0
- data/app/models/shiplog/area_detector.rb +2 -1
- data/app/models/shiplog/change_candidates.rb +4 -0
- data/app/models/shiplog/drafter.rb +4 -2
- data/app/models/shiplog/entry.rb +76 -6
- data/app/models/shiplog/markdown.rb +233 -0
- data/app/models/shiplog/newsletter/content.rb +172 -0
- data/app/models/shiplog/newsletter/csv.rb +32 -0
- data/app/models/shiplog/newsletter/recipients.rb +92 -0
- data/app/models/shiplog/newsletter/tokens.rb +77 -0
- data/app/models/shiplog/newsletter.rb +180 -0
- data/app/models/shiplog/newsletter_delivery.rb +83 -0
- data/app/models/shiplog/newsletter_subscriber.rb +158 -0
- data/app/models/shiplog/period.rb +129 -0
- data/app/models/shiplog/public_links.rb +47 -0
- data/app/models/shiplog/rewriter/announcement_prompt.rb +70 -0
- data/app/models/shiplog/rewriter.rb +23 -3
- data/app/models/shiplog/translator/prompt.rb +2 -2
- data/app/models/shiplog/translator.rb +1 -1
- data/app/views/shiplog/admin/_nav.html.erb +3 -0
- data/app/views/shiplog/admin/entries/_announcement.html.erb +52 -0
- data/app/views/shiplog/admin/entries/_badges.html.erb +1 -1
- data/app/views/shiplog/admin/entries/_newsletter.html.erb +42 -0
- data/app/views/shiplog/admin/entries/edit.html.erb +8 -2
- data/app/views/shiplog/admin/entries/index.html.erb +9 -0
- data/app/views/shiplog/admin/entries/newsletters/show.html.erb +46 -0
- data/app/views/shiplog/admin/newsletter_subscribers/index.html.erb +83 -0
- data/app/views/shiplog/changelog_entries/announcement.html.erb +16 -0
- data/app/views/shiplog/changelogs/_month.html.erb +10 -3
- data/app/views/shiplog/changelogs/show.html.erb +4 -1
- data/app/views/shiplog/newsletter/_form.html.erb +25 -0
- data/app/views/shiplog/newsletter/_page.html.erb +14 -0
- data/app/views/shiplog/newsletter/confirmations/create.html.erb +5 -0
- data/app/views/shiplog/newsletter/confirmations/show.html.erb +5 -0
- data/app/views/shiplog/newsletter/invalid.html.erb +4 -0
- data/app/views/shiplog/newsletter/preferences/destroy.html.erb +4 -0
- data/app/views/shiplog/newsletter/preferences/show.html.erb +23 -0
- data/app/views/shiplog/newsletter/rate_limited.html.erb +4 -0
- data/app/views/shiplog/newsletter/subscriptions/show.html.erb +16 -0
- data/app/views/shiplog/newsletter/unsubscriptions/create.html.erb +4 -0
- data/app/views/shiplog/newsletter/unsubscriptions/show.html.erb +5 -0
- data/app/views/shiplog/newsletter_mailer/announcement.html.erb +113 -0
- data/app/views/shiplog/newsletter_mailer/announcement.text.erb +38 -0
- data/app/views/shiplog/newsletter_mailer/confirmation.html.erb +43 -0
- data/app/views/shiplog/newsletter_mailer/confirmation.text.erb +12 -0
- data/config/admin_routes.rb +12 -0
- data/config/locales/en.yml +147 -0
- data/config/locales/es.yml +147 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20261028000001_shiplog_0_4_0.rb +19 -0
- data/db/migrate/20261028000002_shiplog_0_4_0_newsletter.rb +44 -0
- data/lib/generators/shiplog/install/install_generator.rb +14 -1
- data/lib/generators/shiplog/install/templates/initializer.rb.tt +50 -0
- data/lib/shiplog/classes.rb +14 -1
- data/lib/shiplog/configuration.rb +135 -3
- data/lib/shiplog/engine.rb +2 -0
- data/{app/helpers → lib}/shiplog/head_tags_helper.rb +15 -5
- data/lib/shiplog/previews/shiplog/newsletter_mailer_preview.rb +77 -0
- data/lib/shiplog/version.rb +1 -1
- data/lib/shiplog.rb +33 -0
- data/lib/tasks/shiplog.rake +22 -0
- metadata +57 -2
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<%# The newsletter's HTML part: one 600px table column (fluid below that), every style inline (email clients drop
|
|
2
|
+
stylesheets), absolute image URLs with a width and an alt text, light colours that hold up when a client inverts
|
|
3
|
+
them for dark mode. @content is a Shiplog::Newsletter::Content: title, month, intro_html / highlights / rest_html
|
|
4
|
+
when the split rule applies (see Content), else the whole body as html; @unsubscribe_url and @preferences_url are
|
|
5
|
+
the recipient's own links. Copy this file into your app to change the design; keep the unsubscribe and preferences
|
|
6
|
+
links and the postal address (the List-Unsubscribe headers are set by the mailer). %>
|
|
7
|
+
<% palette = shiplog_mail_palette %>
|
|
8
|
+
<% font = shiplog_mail_font %>
|
|
9
|
+
<% accent = @content.accent %>
|
|
10
|
+
<% split = @content.highlights_shown? %>
|
|
11
|
+
<!DOCTYPE html>
|
|
12
|
+
<html lang="<%= I18n.locale %>">
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="utf-8">
|
|
15
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
16
|
+
<meta name="color-scheme" content="light dark">
|
|
17
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
18
|
+
<title><%= @content.title %></title>
|
|
19
|
+
</head>
|
|
20
|
+
<body style="margin:0;padding:0;background-color:<%= palette[:page] %>;color:<%= palette[:text] %>;font-family:<%= font %>;">
|
|
21
|
+
<div style="display:none;max-height:0;overflow:hidden;font-size:1px;line-height:1px;color:<%= palette[:page] %>;"><%= @content.preheader %></div>
|
|
22
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:<%= palette[:page] %>;">
|
|
23
|
+
<tr>
|
|
24
|
+
<td align="center" style="padding:24px 12px;">
|
|
25
|
+
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:600px;">
|
|
26
|
+
<tr>
|
|
27
|
+
<td style="padding:0 4px 14px;font-family:<%= font %>;">
|
|
28
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
29
|
+
<tr>
|
|
30
|
+
<td style="font-family:<%= font %>;font-size:16px;font-weight:700;color:<%= palette[:text] %>;vertical-align:middle;">
|
|
31
|
+
<% if @content.logo_url %>
|
|
32
|
+
<%= image_tag @content.logo_url, alt: @content.product_name, width: 140, style: "display:block;width:140px;max-width:140px;height:auto;border:0;" %>
|
|
33
|
+
<% else %>
|
|
34
|
+
<%= @content.product_name %>
|
|
35
|
+
<% end %>
|
|
36
|
+
</td>
|
|
37
|
+
<% if @content.view_url %>
|
|
38
|
+
<td align="right" style="font-family:<%= font %>;font-size:12px;line-height:1.5;color:<%= palette[:muted] %>;vertical-align:middle;white-space:nowrap;">
|
|
39
|
+
<%= link_to t("shiplog.newsletter.mail.view_in_browser"), @content.view_url, style: "color:#{palette[:muted]};text-decoration:underline;" %>
|
|
40
|
+
</td>
|
|
41
|
+
<% end %>
|
|
42
|
+
</tr>
|
|
43
|
+
</table>
|
|
44
|
+
</td>
|
|
45
|
+
</tr>
|
|
46
|
+
<tr>
|
|
47
|
+
<td style="background-color:<%= palette[:card] %>;border:1px solid <%= palette[:border] %>;border-radius:10px;padding:36px 32px 28px;font-family:<%= font %>;">
|
|
48
|
+
<p style="margin:0 0 10px;font-family:<%= font %>;font-size:12px;line-height:1.5;letter-spacing:0.1em;text-transform:uppercase;font-weight:600;color:<%= accent %>;"><%= t("shiplog.categories.announcement", default: "Monthly update") %> · <%= @content.month %></p>
|
|
49
|
+
<h1 style="margin:0 0 16px;font-family:<%= font %>;font-size:32px;line-height:1.2;font-weight:800;letter-spacing:-0.01em;color:<%= palette[:text] %>;"><%= @content.title %></h1>
|
|
50
|
+
<% if split %>
|
|
51
|
+
<%= @content.intro_html %>
|
|
52
|
+
<% else %>
|
|
53
|
+
<%= @content.html %>
|
|
54
|
+
<%= shiplog_mail_button t("shiplog.newsletter.mail.changelog_button"), @content.changelog_url, accent %>
|
|
55
|
+
<% end %>
|
|
56
|
+
</td>
|
|
57
|
+
</tr>
|
|
58
|
+
<% if split %>
|
|
59
|
+
<% @content.highlights.each do |highlight| %>
|
|
60
|
+
<tr>
|
|
61
|
+
<td style="padding:16px 0 0;">
|
|
62
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:<%= palette[:card] %>;border:1px solid <%= palette[:border] %>;border-radius:10px;">
|
|
63
|
+
<% if highlight.image? %>
|
|
64
|
+
<tr>
|
|
65
|
+
<td style="padding:0;line-height:0;">
|
|
66
|
+
<%= shiplog_mail_screenshot(highlight) %>
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
69
|
+
<% end %>
|
|
70
|
+
<tr>
|
|
71
|
+
<td style="padding:24px 28px 26px;font-family:<%= font %>;">
|
|
72
|
+
<h2 style="margin:0 0 8px;font-family:<%= font %>;font-size:21px;line-height:1.3;font-weight:700;color:<%= palette[:text] %>;"><%= link_to highlight.title, highlight.url, style: "color:#{palette[:text]};text-decoration:none;" %></h2>
|
|
73
|
+
<% if highlight.summary.present? %>
|
|
74
|
+
<p style="margin:0;font-family:<%= font %>;font-size:16px;line-height:1.6;color:<%= palette[:muted] %>;"><%= highlight.summary %></p>
|
|
75
|
+
<% end %>
|
|
76
|
+
<%= shiplog_mail_button t("shiplog.newsletter.mail.highlight_button"), highlight.url, accent, margin: "18px 0 0" %>
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
</table>
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
<% end %>
|
|
83
|
+
<tr>
|
|
84
|
+
<td style="padding:16px 0 0;">
|
|
85
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:<%= palette[:card] %>;border:1px solid <%= palette[:border] %>;border-radius:10px;">
|
|
86
|
+
<tr>
|
|
87
|
+
<td style="padding:28px 32px 28px;font-family:<%= font %>;">
|
|
88
|
+
<%= @content.rest_html %>
|
|
89
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:24px 0 0;"><tr><td style="border-top:1px solid <%= palette[:border] %>;font-size:0;line-height:0;"> </td></tr></table>
|
|
90
|
+
<%= shiplog_mail_button t("shiplog.newsletter.mail.changelog_button"), @content.changelog_url, accent, margin: "20px 0 0" %>
|
|
91
|
+
</td>
|
|
92
|
+
</tr>
|
|
93
|
+
</table>
|
|
94
|
+
</td>
|
|
95
|
+
</tr>
|
|
96
|
+
<% end %>
|
|
97
|
+
<tr>
|
|
98
|
+
<td style="padding:28px 12px 8px;font-family:<%= font %>;font-size:12px;line-height:1.7;color:<%= palette[:muted] %>;text-align:center;">
|
|
99
|
+
<%= t("shiplog.newsletter.mail.reason", product: @content.product_name) %><br>
|
|
100
|
+
<%= link_to t("shiplog.newsletter.mail.unsubscribe"), @unsubscribe_url, style: "color:#{palette[:muted]};text-decoration:underline;" %>
|
|
101
|
+
·
|
|
102
|
+
<%= link_to t("shiplog.newsletter.mail.preferences"), @preferences_url, style: "color:#{palette[:muted]};text-decoration:underline;" %>
|
|
103
|
+
<% if @content.address.present? %>
|
|
104
|
+
<br><%= @content.address %>
|
|
105
|
+
<% end %>
|
|
106
|
+
</td>
|
|
107
|
+
</tr>
|
|
108
|
+
</table>
|
|
109
|
+
</td>
|
|
110
|
+
</tr>
|
|
111
|
+
</table>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<%# The newsletter's text part: title, month, the intro, the highlights ("Title — sentence", then the URL) and the rest
|
|
2
|
+
of the body when the split rule applies (see Shiplog::Newsletter::Content), else the whole body; then the links. %>
|
|
3
|
+
<%= @content.title %>
|
|
4
|
+
<%= t("shiplog.categories.announcement", default: "Monthly update") %> · <%= @content.month %>
|
|
5
|
+
<% if @content.view_url %>
|
|
6
|
+
|
|
7
|
+
<%= t("shiplog.newsletter.mail.view_in_browser") %>: <%= @content.view_url %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if @content.highlights_shown? %>
|
|
10
|
+
<% if @content.intro_text.present? %>
|
|
11
|
+
|
|
12
|
+
<%= @content.intro_text %>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% @content.highlights.each do |highlight| %>
|
|
15
|
+
|
|
16
|
+
<%= highlight.title %><% if highlight.summary.present? %> — <%= highlight.summary %><% end %>
|
|
17
|
+
<%= highlight.url %>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% if @content.rest_text.present? %>
|
|
20
|
+
|
|
21
|
+
<%= @content.rest_text %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% else %>
|
|
24
|
+
|
|
25
|
+
<%= @content.text %>
|
|
26
|
+
<% end %>
|
|
27
|
+
<% if @content.changelog_url %>
|
|
28
|
+
|
|
29
|
+
<%= t("shiplog.newsletter.mail.changelog_button") %>: <%= @content.changelog_url %>
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
32
|
+
--
|
|
33
|
+
<%= t("shiplog.newsletter.mail.reason", product: @content.product_name) %>
|
|
34
|
+
<%= t("shiplog.newsletter.mail.unsubscribe") %>: <%= @unsubscribe_url %>
|
|
35
|
+
<%= t("shiplog.newsletter.mail.preferences") %>: <%= @preferences_url %>
|
|
36
|
+
<% if @content.address.present? %>
|
|
37
|
+
<%= @content.address %>
|
|
38
|
+
<% end %>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<%# The double opt-in email: a button (and the link as text) the visitor opens to confirm; @subscriber, @confirmation_url
|
|
2
|
+
and @product_name. The link works for 7 days; a page with a button is what it opens (a POST confirms), so a
|
|
3
|
+
link scanner that prefetches it confirms nothing. %>
|
|
4
|
+
<% palette = shiplog_mail_palette %>
|
|
5
|
+
<% font = shiplog_mail_font %>
|
|
6
|
+
<% accent = Shiplog.config.newsletter_accent || "#0969da" %>
|
|
7
|
+
<!DOCTYPE html>
|
|
8
|
+
<html lang="<%= I18n.locale %>">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="utf-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<meta name="color-scheme" content="light dark">
|
|
13
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
14
|
+
<title><%= t("shiplog.newsletter.mail.confirm_subject", product: @product_name) %></title>
|
|
15
|
+
</head>
|
|
16
|
+
<body style="margin:0;padding:0;background-color:<%= palette[:page] %>;color:<%= palette[:text] %>;font-family:<%= font %>;">
|
|
17
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:<%= palette[:page] %>;">
|
|
18
|
+
<tr>
|
|
19
|
+
<td align="center" style="padding:24px 12px;">
|
|
20
|
+
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:600px;">
|
|
21
|
+
<tr>
|
|
22
|
+
<td style="background-color:<%= palette[:card] %>;border-radius:8px;padding:32px;font-family:<%= font %>;">
|
|
23
|
+
<% if Shiplog.config.newsletter_logo_url %>
|
|
24
|
+
<%= image_tag Shiplog.config.newsletter_logo_url, alt: @product_name, width: 140, style: "display:block;width:140px;max-width:140px;height:auto;border:0;margin:0 0 24px;" %>
|
|
25
|
+
<% end %>
|
|
26
|
+
<h1 style="margin:0 0 16px;font-family:<%= font %>;font-size:22px;line-height:1.3;font-weight:700;color:<%= palette[:text] %>;"><%= t("shiplog.newsletter.mail.confirm_heading") %></h1>
|
|
27
|
+
<p style="margin:0 0 12px;font-family:<%= font %>;font-size:16px;line-height:1.6;color:<%= palette[:text] %>;"><%= t("shiplog.newsletter.mail.confirm_intro", product: @product_name, email: @subscriber.email) %></p>
|
|
28
|
+
<%= shiplog_mail_button t("shiplog.newsletter.mail.confirm_button"), @confirmation_url, accent %>
|
|
29
|
+
<p style="margin:24px 0 0;font-family:<%= font %>;font-size:13px;line-height:1.6;color:<%= palette[:muted] %>;"><%= t("shiplog.newsletter.mail.confirm_expiry") %></p>
|
|
30
|
+
<p style="margin:12px 0 0;font-family:<%= font %>;font-size:12px;line-height:1.6;color:<%= palette[:muted] %>;word-break:break-all;"><%= t("shiplog.newsletter.mail.confirm_link_text") %> <%= link_to @confirmation_url, @confirmation_url, style: "color:#{palette[:muted]};" %></p>
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
<% if Shiplog.config.newsletter_address.present? %>
|
|
34
|
+
<tr>
|
|
35
|
+
<td style="padding:24px 12px;font-family:<%= font %>;font-size:12px;line-height:1.7;color:<%= palette[:muted] %>;text-align:center;"><%= Shiplog.config.newsletter_address %></td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</table>
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
</table>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%# The double opt-in email's text part. %>
|
|
2
|
+
<%= t("shiplog.newsletter.mail.confirm_heading") %>
|
|
3
|
+
|
|
4
|
+
<%= t("shiplog.newsletter.mail.confirm_intro", product: @product_name, email: @subscriber.email) %>
|
|
5
|
+
|
|
6
|
+
<%= t("shiplog.newsletter.mail.confirm_button") %>: <%= @confirmation_url %>
|
|
7
|
+
|
|
8
|
+
<%= t("shiplog.newsletter.mail.confirm_expiry") %>
|
|
9
|
+
<% if Shiplog.config.newsletter_address.present? %>
|
|
10
|
+
|
|
11
|
+
<%= Shiplog.config.newsletter_address %>
|
|
12
|
+
<% end %>
|
data/config/admin_routes.rb
CHANGED
|
@@ -5,6 +5,15 @@ 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
|
+
# An announcement's text as a file to paste elsewhere: ?as=markdown, html or text, in the default locale or
|
|
9
|
+
# ?export_locale= (not :locale, which a host's locale-switching around_action would read; see translations).
|
|
10
|
+
get "export", to: "exports#show", as: :export
|
|
11
|
+
resource :announcement_notification, only: :create
|
|
12
|
+
# The newsletter of an announcement: the confirmation step with the recipient counts (show), the send
|
|
13
|
+
# (create), a test to the admin's address, and the retry of failed deliveries.
|
|
14
|
+
resource :newsletter, only: %i[show create]
|
|
15
|
+
resource :newsletter_test, only: :create, path: "newsletter/test"
|
|
16
|
+
resource :newsletter_retry, only: :create, path: "newsletter/retry"
|
|
8
17
|
# The locale segment is :translation_locale, not :locale: a host's around_action that switches the locale
|
|
9
18
|
# from params[:locale] must not read it (a configured translation locale may not be an available one).
|
|
10
19
|
locale = { translation_locale: /[A-Za-z][A-Za-z0-9_-]*/ }
|
|
@@ -15,6 +24,9 @@ Shiplog::Admin::Engine.routes.draw do
|
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
26
|
end
|
|
27
|
+
resources :announcements, only: :create
|
|
28
|
+
# The newsletter's subscribers: counts, a searchable list (?q=), removal, and the CSV export (.csv).
|
|
29
|
+
resources :newsletter_subscribers, only: %i[index destroy], path: "newsletter"
|
|
18
30
|
resource :area_detection, only: :create, path: "areas/detection"
|
|
19
31
|
resource :area_translation, only: :create, path: "areas/translation"
|
|
20
32
|
resources :areas, only: %i[index update destroy] do
|
data/config/locales/en.yml
CHANGED
|
@@ -14,6 +14,7 @@ en:
|
|
|
14
14
|
categories:
|
|
15
15
|
feature: New
|
|
16
16
|
change: Improved
|
|
17
|
+
announcement: Monthly update
|
|
17
18
|
empty: Nothing here yet
|
|
18
19
|
date_format: "%-d %B %Y"
|
|
19
20
|
roadmap:
|
|
@@ -55,17 +56,161 @@ en:
|
|
|
55
56
|
nav_label: More changelog entries
|
|
56
57
|
newer: "← Newer: %{title}"
|
|
57
58
|
older: "Older: %{title} →"
|
|
59
|
+
announcements:
|
|
60
|
+
read: "Read the %{month} summary"
|
|
61
|
+
back: "← Back to the changelog"
|
|
62
|
+
skeleton:
|
|
63
|
+
features: New
|
|
64
|
+
also_improved: Also improved
|
|
58
65
|
feed:
|
|
59
66
|
title_with_product: "%{product} changelog"
|
|
60
67
|
link_title: Changelog (Atom)
|
|
61
68
|
subscribe: Subscribe (Atom)
|
|
69
|
+
newsletter:
|
|
70
|
+
title: Newsletter
|
|
71
|
+
product: Product updates
|
|
72
|
+
back: "← Back to the changelog"
|
|
73
|
+
form:
|
|
74
|
+
title: Get the monthly summary by email
|
|
75
|
+
hint: One email a month, no more. Unsubscribe any time.
|
|
76
|
+
email_label: Email address
|
|
77
|
+
email_placeholder: you@example.com
|
|
78
|
+
submit: Subscribe
|
|
79
|
+
trap_label: Leave this field empty
|
|
80
|
+
invalid_email: Enter a valid email address.
|
|
81
|
+
sent:
|
|
82
|
+
heading: Check your inbox
|
|
83
|
+
text: We sent you a link to confirm your subscription. Open it to start receiving the monthly summary. If nothing arrives within a few minutes, look in your spam folder.
|
|
84
|
+
confirm:
|
|
85
|
+
heading: Confirm your subscription
|
|
86
|
+
text: "Click the button to receive the monthly summary at %{email}."
|
|
87
|
+
button: Confirm
|
|
88
|
+
done_heading: You're subscribed
|
|
89
|
+
done_text: "The monthly summary will arrive at %{email}. You can change the language or unsubscribe any time from your preferences."
|
|
90
|
+
preferences:
|
|
91
|
+
heading: Your subscription
|
|
92
|
+
email: Email
|
|
93
|
+
status: Status
|
|
94
|
+
statuses:
|
|
95
|
+
pending: Waiting for confirmation
|
|
96
|
+
confirmed: Subscribed
|
|
97
|
+
unsubscribed: Unsubscribed
|
|
98
|
+
language: Language
|
|
99
|
+
save: Save
|
|
100
|
+
saved: Preferences saved.
|
|
101
|
+
unsubscribe: Unsubscribe
|
|
102
|
+
unsubscribed: You're unsubscribed.
|
|
103
|
+
resubscribe: Resubscribe
|
|
104
|
+
resubscribed: You're subscribed again.
|
|
105
|
+
delete: Delete my data
|
|
106
|
+
delete_help: Deleting your data removes your address from our records entirely, including the record that you unsubscribed.
|
|
107
|
+
deleted_heading: Your data was deleted
|
|
108
|
+
deleted_text: Your email address is no longer stored. You can subscribe again from the changelog at any time.
|
|
109
|
+
unsubscribe:
|
|
110
|
+
heading: Unsubscribe
|
|
111
|
+
text: "Stop receiving the monthly summary at %{email}?"
|
|
112
|
+
button: Unsubscribe
|
|
113
|
+
done_heading: You're unsubscribed
|
|
114
|
+
done_text: "%{email} will not receive the monthly summary anymore."
|
|
115
|
+
invalid:
|
|
116
|
+
heading: This link is not valid
|
|
117
|
+
text: It may have expired or already been used. Subscribe again from the changelog to get a new one.
|
|
118
|
+
rate_limited:
|
|
119
|
+
heading: Too many attempts
|
|
120
|
+
text: Please try again in an hour.
|
|
121
|
+
mail:
|
|
122
|
+
view_in_browser: View in browser
|
|
123
|
+
changelog_button: See everything that shipped
|
|
124
|
+
highlight_button: See how it works
|
|
125
|
+
reason: "You receive this email because you subscribed to the %{product} monthly summary."
|
|
126
|
+
unsubscribe: Unsubscribe
|
|
127
|
+
preferences: Preferences
|
|
128
|
+
confirm_subject: "Confirm your subscription to the %{product} newsletter"
|
|
129
|
+
confirm_heading: One more step
|
|
130
|
+
confirm_intro: "Confirm that you want to receive the %{product} monthly summary at %{email}."
|
|
131
|
+
confirm_button: Confirm my subscription
|
|
132
|
+
confirm_expiry: "The link works for 7 days. If you did not ask for this, ignore this email: nothing is sent without confirmation."
|
|
133
|
+
confirm_link_text: "Or copy this link:"
|
|
62
134
|
admin:
|
|
135
|
+
newsletter:
|
|
136
|
+
title: Newsletter
|
|
137
|
+
help: The visitors who subscribed through the changelog's form. Your own users (config.newsletter_recipients) are not listed here.
|
|
138
|
+
total: Total
|
|
139
|
+
search: Search
|
|
140
|
+
search_placeholder: Search by email
|
|
141
|
+
clear: Clear
|
|
142
|
+
export: Download CSV
|
|
143
|
+
remove: Remove
|
|
144
|
+
removed: "%{email} removed."
|
|
145
|
+
empty: No subscribers yet.
|
|
146
|
+
no_match: "No subscriber matches “%{query}”."
|
|
147
|
+
statuses:
|
|
148
|
+
pending: Pending
|
|
149
|
+
confirmed: Confirmed
|
|
150
|
+
unsubscribed: Unsubscribed
|
|
151
|
+
columns:
|
|
152
|
+
email: Email
|
|
153
|
+
locale: Language
|
|
154
|
+
status: Status
|
|
155
|
+
source: Signed up on
|
|
156
|
+
subscribed_at: Signed up
|
|
157
|
+
recipients: Recipients
|
|
158
|
+
heading: Newsletter
|
|
159
|
+
not_sent: Not sent yet.
|
|
160
|
+
progress: "%{sent} sent, %{failed} failed, %{pending} pending."
|
|
161
|
+
stalled:
|
|
162
|
+
one: "%{count} has been pending for more than an hour (the worker may have died): Retry sends it again."
|
|
163
|
+
other: "%{count} have been pending for more than an hour (the worker may have died): Retry sends them again."
|
|
164
|
+
retry: Retry failed
|
|
165
|
+
retry_queued: Retrying the failed deliveries.
|
|
166
|
+
send: Send…
|
|
167
|
+
send_blocked: "Sending is not possible yet: %{reasons}."
|
|
168
|
+
blockers:
|
|
169
|
+
not_approved: the announcement is not approved
|
|
170
|
+
translations: "a translation is missing or stale (%{locales})"
|
|
171
|
+
from: config.newsletter_from is not set
|
|
172
|
+
address: config.newsletter_address is not set
|
|
173
|
+
base_url: config.canonical_url is not set (the links in emails must be absolute)
|
|
174
|
+
test: "Send a test to %{email}"
|
|
175
|
+
test_locale: Language
|
|
176
|
+
test_sent: "Test sent to %{email} (%{language})."
|
|
177
|
+
test_failed: "The test could not be sent (%{error}). See the log."
|
|
178
|
+
test_no_email: config.current_admin gives no email address, so no test can be sent.
|
|
179
|
+
confirm_title: "Send the newsletter: %{title}"
|
|
180
|
+
recipients: Recipients
|
|
181
|
+
recipients_none: Nobody to send to yet.
|
|
182
|
+
recipients_error: "The recipients could not be listed (%{error}). See the log for details."
|
|
183
|
+
confirm_help: Each recipient receives the email once, in their language. Sending again later only reaches the recipients who have not received it.
|
|
184
|
+
confirm_resend_help: Recipients who already received it are skipped.
|
|
185
|
+
confirm_button:
|
|
186
|
+
one: "Send to %{count} recipient"
|
|
187
|
+
other: "Send to %{count} recipients"
|
|
188
|
+
queued: The newsletter is being sent. Refresh in a moment.
|
|
189
|
+
back: Back to the announcement
|
|
190
|
+
announcements:
|
|
191
|
+
draft: "Draft announcement for %{month}"
|
|
192
|
+
drafted: Announcement draft ready.
|
|
193
|
+
nothing_to_announce: That month has no approved feature or change to announce yet.
|
|
194
|
+
publishing: Announcement
|
|
195
|
+
view_page: View the public page
|
|
196
|
+
external: Published by your app (config.announcements = :external).
|
|
197
|
+
announced: "Your app was notified (on_announcement_approved) at %{time}."
|
|
198
|
+
waiting_for_translations: Your app will be notified once every translation is current.
|
|
199
|
+
announcing: Notifying your app…
|
|
200
|
+
not_approved: Approve it to publish it and notify your app.
|
|
201
|
+
export: "Export:"
|
|
202
|
+
plain_text: Plain text
|
|
203
|
+
preview: Preview
|
|
204
|
+
notify_now: Notify now
|
|
205
|
+
notify_now_hint: Calls your app's hook now, without waiting for missing translations (English is used for them).
|
|
206
|
+
notify_queued: Your app will be notified in a moment.
|
|
63
207
|
title: Shiplog admin
|
|
64
208
|
nav:
|
|
65
209
|
drafts: Drafts
|
|
66
210
|
board: Roadmap board
|
|
67
211
|
sync: Sync
|
|
68
212
|
areas: Areas
|
|
213
|
+
newsletter: Newsletter
|
|
69
214
|
areas:
|
|
70
215
|
title: Product areas
|
|
71
216
|
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.
|
|
@@ -126,6 +271,7 @@ en:
|
|
|
126
271
|
area_gone: no longer exists
|
|
127
272
|
regenerate_warning: Regenerating unpublishes this entry until re-approved.
|
|
128
273
|
source_changed: GitHub text changed since this draft
|
|
274
|
+
entries_changed: The month's entries changed since this draft
|
|
129
275
|
rewrite_error: Rewrite failed
|
|
130
276
|
not_rewritten: Not rewritten yet
|
|
131
277
|
public_text: Public text
|
|
@@ -175,6 +321,7 @@ en:
|
|
|
175
321
|
roadmap: Roadmap
|
|
176
322
|
feature: Feature
|
|
177
323
|
change: Change
|
|
324
|
+
announcement: Announcement
|
|
178
325
|
columns:
|
|
179
326
|
entry: Entry
|
|
180
327
|
kind: Kind
|
data/config/locales/es.yml
CHANGED
|
@@ -18,6 +18,7 @@ es:
|
|
|
18
18
|
categories:
|
|
19
19
|
feature: Nuevo
|
|
20
20
|
change: Mejorado
|
|
21
|
+
announcement: Resumen mensual
|
|
21
22
|
empty: Todavía no hay nada
|
|
22
23
|
date_format: "%-d de %B de %Y"
|
|
23
24
|
roadmap:
|
|
@@ -59,17 +60,161 @@ es:
|
|
|
59
60
|
nav_label: Más cambios
|
|
60
61
|
newer: "← Más reciente: %{title}"
|
|
61
62
|
older: "Anterior: %{title} →"
|
|
63
|
+
announcements:
|
|
64
|
+
read: "Leer el resumen de %{month}"
|
|
65
|
+
back: "← Volver al registro de cambios"
|
|
66
|
+
skeleton:
|
|
67
|
+
features: Novedades
|
|
68
|
+
also_improved: También mejoramos
|
|
62
69
|
feed:
|
|
63
70
|
title_with_product: "Registro de cambios de %{product}"
|
|
64
71
|
link_title: Registro de cambios (Atom)
|
|
65
72
|
subscribe: Suscribirse (Atom)
|
|
73
|
+
newsletter:
|
|
74
|
+
title: Boletín
|
|
75
|
+
product: Novedades del producto
|
|
76
|
+
back: "← Volver al registro de cambios"
|
|
77
|
+
form:
|
|
78
|
+
title: Recibe el resumen mensual por correo
|
|
79
|
+
hint: Un correo al mes, nada más. Date de baja cuando quieras.
|
|
80
|
+
email_label: Dirección de correo
|
|
81
|
+
email_placeholder: tu@ejemplo.com
|
|
82
|
+
submit: Suscribirme
|
|
83
|
+
trap_label: Deja este campo vacío
|
|
84
|
+
invalid_email: Escribe una dirección de correo válida.
|
|
85
|
+
sent:
|
|
86
|
+
heading: Revisa tu bandeja de entrada
|
|
87
|
+
text: Te hemos enviado un enlace para confirmar la suscripción. Ábrelo para empezar a recibir el resumen mensual. Si no llega en unos minutos, mira en la carpeta de spam.
|
|
88
|
+
confirm:
|
|
89
|
+
heading: Confirma tu suscripción
|
|
90
|
+
text: "Pulsa el botón para recibir el resumen mensual en %{email}."
|
|
91
|
+
button: Confirmar
|
|
92
|
+
done_heading: Suscripción confirmada
|
|
93
|
+
done_text: "El resumen mensual llegará a %{email}. Puedes cambiar el idioma o darte de baja cuando quieras desde tus preferencias."
|
|
94
|
+
preferences:
|
|
95
|
+
heading: Tu suscripción
|
|
96
|
+
email: Correo
|
|
97
|
+
status: Estado
|
|
98
|
+
statuses:
|
|
99
|
+
pending: Pendiente de confirmación
|
|
100
|
+
confirmed: Suscrito
|
|
101
|
+
unsubscribed: Dado de baja
|
|
102
|
+
language: Idioma
|
|
103
|
+
save: Guardar
|
|
104
|
+
saved: Preferencias guardadas.
|
|
105
|
+
unsubscribe: Darme de baja
|
|
106
|
+
unsubscribed: Te has dado de baja.
|
|
107
|
+
resubscribe: Volver a suscribirme
|
|
108
|
+
resubscribed: Vuelves a estar suscrito.
|
|
109
|
+
delete: Borrar mis datos
|
|
110
|
+
delete_help: Borrar tus datos elimina tu dirección de nuestros registros por completo, incluido el registro de tu baja.
|
|
111
|
+
deleted_heading: Tus datos se han borrado
|
|
112
|
+
deleted_text: Tu dirección de correo ya no está guardada. Puedes volver a suscribirte desde el registro de cambios cuando quieras.
|
|
113
|
+
unsubscribe:
|
|
114
|
+
heading: Darse de baja
|
|
115
|
+
text: "¿Dejar de recibir el resumen mensual en %{email}?"
|
|
116
|
+
button: Darme de baja
|
|
117
|
+
done_heading: Te has dado de baja
|
|
118
|
+
done_text: "%{email} ya no recibirá el resumen mensual."
|
|
119
|
+
invalid:
|
|
120
|
+
heading: Este enlace no es válido
|
|
121
|
+
text: Puede que haya caducado o que ya se haya usado. Vuelve a suscribirte desde el registro de cambios para recibir otro.
|
|
122
|
+
rate_limited:
|
|
123
|
+
heading: Demasiados intentos
|
|
124
|
+
text: Inténtalo de nuevo dentro de una hora.
|
|
125
|
+
mail:
|
|
126
|
+
view_in_browser: Ver en el navegador
|
|
127
|
+
changelog_button: Ver todo lo que hemos lanzado
|
|
128
|
+
highlight_button: Ver cómo funciona
|
|
129
|
+
reason: "Recibes este correo porque te suscribiste al resumen mensual de %{product}."
|
|
130
|
+
unsubscribe: Darme de baja
|
|
131
|
+
preferences: Preferencias
|
|
132
|
+
confirm_subject: "Confirma tu suscripción al boletín de %{product}"
|
|
133
|
+
confirm_heading: Un paso más
|
|
134
|
+
confirm_intro: "Confirma que quieres recibir el resumen mensual de %{product} en %{email}."
|
|
135
|
+
confirm_button: Confirmar mi suscripción
|
|
136
|
+
confirm_expiry: "El enlace funciona durante 7 días. Si no has pedido esto, ignora este correo: no se envía nada sin confirmación."
|
|
137
|
+
confirm_link_text: "O copia este enlace:"
|
|
66
138
|
admin:
|
|
139
|
+
newsletter:
|
|
140
|
+
title: Boletín
|
|
141
|
+
help: Los visitantes que se suscribieron desde el formulario del registro de cambios. Tus propios usuarios (config.newsletter_recipients) no aparecen aquí.
|
|
142
|
+
total: Total
|
|
143
|
+
search: Buscar
|
|
144
|
+
search_placeholder: Buscar por correo
|
|
145
|
+
clear: Limpiar
|
|
146
|
+
export: Descargar CSV
|
|
147
|
+
remove: Eliminar
|
|
148
|
+
removed: "%{email} eliminado."
|
|
149
|
+
empty: Todavía no hay suscriptores.
|
|
150
|
+
no_match: "Ningún suscriptor coincide con “%{query}”."
|
|
151
|
+
statuses:
|
|
152
|
+
pending: Pendiente
|
|
153
|
+
confirmed: Confirmado
|
|
154
|
+
unsubscribed: Dado de baja
|
|
155
|
+
columns:
|
|
156
|
+
email: Correo
|
|
157
|
+
locale: Idioma
|
|
158
|
+
status: Estado
|
|
159
|
+
source: Página de alta
|
|
160
|
+
subscribed_at: Alta
|
|
161
|
+
recipients: Destinatarios
|
|
162
|
+
heading: Boletín
|
|
163
|
+
not_sent: Todavía no se ha enviado.
|
|
164
|
+
progress: "%{sent} enviados, %{failed} fallidos, %{pending} pendientes."
|
|
165
|
+
stalled:
|
|
166
|
+
one: "%{count} lleva más de una hora pendiente (puede que el worker se haya caído): Reintentar lo envía de nuevo."
|
|
167
|
+
other: "%{count} llevan más de una hora pendientes (puede que el worker se haya caído): Reintentar los envía de nuevo."
|
|
168
|
+
retry: Reintentar los fallidos
|
|
169
|
+
retry_queued: Reintentando los envíos fallidos.
|
|
170
|
+
send: Enviar…
|
|
171
|
+
send_blocked: "Todavía no se puede enviar: %{reasons}."
|
|
172
|
+
blockers:
|
|
173
|
+
not_approved: el anuncio no está aprobado
|
|
174
|
+
translations: "falta una traducción o está desactualizada (%{locales})"
|
|
175
|
+
from: config.newsletter_from no está configurado
|
|
176
|
+
address: config.newsletter_address no está configurado
|
|
177
|
+
base_url: config.canonical_url no está configurado (los enlaces de los correos deben ser absolutos)
|
|
178
|
+
test: "Enviar una prueba a %{email}"
|
|
179
|
+
test_locale: Idioma
|
|
180
|
+
test_sent: "Prueba enviada a %{email} (%{language})."
|
|
181
|
+
test_failed: "No se pudo enviar la prueba (%{error}). Consulta el registro."
|
|
182
|
+
test_no_email: config.current_admin no devuelve una dirección de correo, así que no se puede enviar ninguna prueba.
|
|
183
|
+
confirm_title: "Enviar el boletín: %{title}"
|
|
184
|
+
recipients: Destinatarios
|
|
185
|
+
recipients_none: Todavía no hay nadie a quien enviarlo.
|
|
186
|
+
recipients_error: "No se pudo obtener la lista de destinatarios (%{error}). Consulta el registro para más detalles."
|
|
187
|
+
confirm_help: Cada destinatario recibe el correo una sola vez, en su idioma. Si lo envías de nuevo más tarde, solo llega a quienes no lo han recibido.
|
|
188
|
+
confirm_resend_help: Se omite a quienes ya lo recibieron.
|
|
189
|
+
confirm_button:
|
|
190
|
+
one: "Enviar a %{count} destinatario"
|
|
191
|
+
other: "Enviar a %{count} destinatarios"
|
|
192
|
+
queued: El boletín se está enviando. Actualiza la página en un momento.
|
|
193
|
+
back: Volver al anuncio
|
|
194
|
+
announcements:
|
|
195
|
+
draft: "Redactar el anuncio de %{month}"
|
|
196
|
+
drafted: El borrador del anuncio está listo.
|
|
197
|
+
nothing_to_announce: Ese mes aún no tiene ninguna función o cambio aprobado que anunciar.
|
|
198
|
+
publishing: Anuncio
|
|
199
|
+
view_page: Ver la página pública
|
|
200
|
+
external: Lo publica tu aplicación (config.announcements = :external).
|
|
201
|
+
announced: "Se avisó a tu aplicación (on_announcement_approved) el %{time}."
|
|
202
|
+
waiting_for_translations: Se avisará a tu aplicación cuando todas las traducciones estén al día.
|
|
203
|
+
announcing: Avisando a tu aplicación…
|
|
204
|
+
not_approved: Apruébalo para publicarlo y avisar a tu aplicación.
|
|
205
|
+
export: "Exportar:"
|
|
206
|
+
plain_text: Texto sin formato
|
|
207
|
+
preview: Vista previa
|
|
208
|
+
notify_now: Avisar ahora
|
|
209
|
+
notify_now_hint: Llama ahora al hook de tu aplicación, sin esperar las traducciones que falten (se usa el inglés).
|
|
210
|
+
notify_queued: Se avisará a tu aplicación en un momento.
|
|
67
211
|
title: Administración de Shiplog
|
|
68
212
|
nav:
|
|
69
213
|
drafts: Borradores
|
|
70
214
|
board: Tablero de la hoja de ruta
|
|
71
215
|
sync: Sincronización
|
|
72
216
|
areas: Áreas
|
|
217
|
+
newsletter: Boletín
|
|
73
218
|
areas:
|
|
74
219
|
title: Áreas del producto
|
|
75
220
|
help: La IA agrupa las entradas en áreas (como máximo %{max}) mientras las reescribe. Los visitantes pueden filtrar el registro de cambios por área cuando hay dos áreas con entradas publicadas.
|
|
@@ -130,6 +275,7 @@ es:
|
|
|
130
275
|
area_gone: ya no existe
|
|
131
276
|
regenerate_warning: Al regenerar, esta entrada deja de estar publicada hasta que se apruebe de nuevo.
|
|
132
277
|
source_changed: El texto de GitHub cambió desde este borrador
|
|
278
|
+
entries_changed: Las entradas del mes cambiaron desde este borrador
|
|
133
279
|
rewrite_error: Falló la reescritura
|
|
134
280
|
not_rewritten: Todavía sin reescribir
|
|
135
281
|
public_text: Texto público
|
|
@@ -179,6 +325,7 @@ es:
|
|
|
179
325
|
roadmap: Hoja de ruta
|
|
180
326
|
feature: Función
|
|
181
327
|
change: Cambio
|
|
328
|
+
announcement: Anuncio
|
|
182
329
|
columns:
|
|
183
330
|
entry: Entrada
|
|
184
331
|
kind: Tipo
|
data/config/routes.rb
CHANGED
|
@@ -7,5 +7,18 @@ Shiplog::Engine.routes.draw do
|
|
|
7
7
|
format: false, constraints: { slug: /[a-z0-9-]+/ }
|
|
8
8
|
get Shiplog.config.changelog_path, to: "changelogs#show", as: :changelog
|
|
9
9
|
post "shiplog/github/webhook", to: "webhooks#create", as: :github_webhook
|
|
10
|
+
# The newsletter's pages live under shiplog/ (like the webhook), so a host that constrains the engine's mount to
|
|
11
|
+
# the changelog, the roadmap and /shiplog/ lets them through. Links from emails open a page with a button
|
|
12
|
+
# (GET), and the button POSTs: nothing changes state on a GET. A token is URL-safe base64 or base58.
|
|
13
|
+
token = { token: /[A-Za-z0-9_-]+/ }
|
|
14
|
+
get "shiplog/newsletter/subscription", to: "newsletter/subscriptions#show", as: :newsletter_subscription
|
|
15
|
+
post "shiplog/newsletter/subscription", to: "newsletter/subscriptions#create"
|
|
16
|
+
get "shiplog/newsletter/confirm/:token", to: "newsletter/confirmations#show", as: :newsletter_confirmation, constraints: token, format: false
|
|
17
|
+
post "shiplog/newsletter/confirm/:token", to: "newsletter/confirmations#create", constraints: token, format: false
|
|
18
|
+
get "shiplog/newsletter/preferences/:token", to: "newsletter/preferences#show", as: :newsletter_preferences, constraints: token, format: false
|
|
19
|
+
patch "shiplog/newsletter/preferences/:token", to: "newsletter/preferences#update", constraints: token, format: false
|
|
20
|
+
delete "shiplog/newsletter/preferences/:token", to: "newsletter/preferences#destroy", constraints: token, format: false
|
|
21
|
+
get "shiplog/newsletter/unsubscribe/:token", to: "newsletter/unsubscriptions#show", as: :newsletter_unsubscription, constraints: token, format: false
|
|
22
|
+
post "shiplog/newsletter/unsubscribe/:token", to: "newsletter/unsubscriptions#create", constraints: token, format: false
|
|
10
23
|
get "shiplog/assets/:version/:name", to: "frontends#show", as: :frontend, constraints: { version: /[^\/]+/, name: /[a-z_]+\.(css|js)/ }
|
|
11
24
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Everything 0.4.0 adds to the schema, one section per feature. Additive and reversible.
|
|
2
|
+
class Shiplog040 < ActiveRecord::Migration[8.1]
|
|
3
|
+
def change
|
|
4
|
+
# --- Monthly announcements ---
|
|
5
|
+
# The subject of an announcement entry: one row per month (starts_on is its first day, ends_on its last), so
|
|
6
|
+
# Shiplog::Entry keeps its polymorphic subject and its unique (subject, kind) index makes drafting idempotent.
|
|
7
|
+
create_table :shiplog_periods do |t|
|
|
8
|
+
t.date :starts_on, null: false
|
|
9
|
+
t.date :ends_on, null: false
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
add_index :shiplog_periods, :starts_on, unique: true
|
|
13
|
+
|
|
14
|
+
# When config.on_announcement_approved was called for the announcement's current approval: set once it is
|
|
15
|
+
# approved and every configured locale has a current translation, cleared when it returns to draft, so the
|
|
16
|
+
# hook runs once per approval.
|
|
17
|
+
add_column :shiplog_entries, :announced_at, :datetime
|
|
18
|
+
end
|
|
19
|
+
end
|