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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Textos de Shiplog en español. Los hosts pueden sobrescribir cualquier clave en sus propios archivos de idioma.
|
|
2
|
+
es:
|
|
3
|
+
activerecord:
|
|
4
|
+
attributes:
|
|
5
|
+
shiplog/entry:
|
|
6
|
+
changelog_kind: Categoría
|
|
7
|
+
# Los nombres de los meses que usa el formato de fecha de Shiplog (rails-i18n los trae también; son los mismos).
|
|
8
|
+
date:
|
|
9
|
+
month_names: [~, enero, febrero, marzo, abril, mayo, junio, julio, agosto, septiembre, octubre, noviembre, diciembre]
|
|
10
|
+
shiplog:
|
|
11
|
+
columns:
|
|
12
|
+
in_progress: En desarrollo
|
|
13
|
+
coming_soon: Próximamente
|
|
14
|
+
later: Más adelante
|
|
15
|
+
in_progress_description: Lo que estamos construyendo ahora
|
|
16
|
+
coming_soon_description: Lo siguiente en la lista
|
|
17
|
+
later_description: En nuestro radar
|
|
18
|
+
categories:
|
|
19
|
+
feature: Nuevo
|
|
20
|
+
change: Mejorado
|
|
21
|
+
empty: Todavía no hay nada
|
|
22
|
+
date_format: "%-d de %B de %Y"
|
|
23
|
+
roadmap:
|
|
24
|
+
title: Hoja de ruta
|
|
25
|
+
lede: En qué estamos trabajando y qué viene después.
|
|
26
|
+
parts:
|
|
27
|
+
one: Incluye %{count} parte
|
|
28
|
+
other: Incluye %{count} partes
|
|
29
|
+
shipped_heading: Lanzado recientemente
|
|
30
|
+
shipped_date_format: "%-d de %B"
|
|
31
|
+
full_changelog: Ver el registro de cambios completo
|
|
32
|
+
footnote: ""
|
|
33
|
+
suggest: Sugerir una función
|
|
34
|
+
switcher:
|
|
35
|
+
label: Actualizaciones del producto
|
|
36
|
+
roadmap: Hoja de ruta
|
|
37
|
+
changelog: Registro de cambios
|
|
38
|
+
changelog:
|
|
39
|
+
title: Registro de cambios
|
|
40
|
+
lede: Nuevas funciones y mejoras, a medida que se lanzan.
|
|
41
|
+
pagination:
|
|
42
|
+
label: Paginación
|
|
43
|
+
older: Anteriores
|
|
44
|
+
newer: Más recientes
|
|
45
|
+
search:
|
|
46
|
+
label: Buscar en el registro de cambios
|
|
47
|
+
submit: Buscar
|
|
48
|
+
summary:
|
|
49
|
+
one: "%{count} resultado para “%{query}”"
|
|
50
|
+
other: "%{count} resultados para “%{query}”"
|
|
51
|
+
clear: Borrar búsqueda
|
|
52
|
+
empty: "Ninguna entrada coincide con “%{query}”."
|
|
53
|
+
areas:
|
|
54
|
+
label: Filtrar por área
|
|
55
|
+
all: Todas
|
|
56
|
+
permalink:
|
|
57
|
+
title: "%{title} · %{changelog}"
|
|
58
|
+
back: "← Volver al registro de cambios"
|
|
59
|
+
nav_label: Más cambios
|
|
60
|
+
newer: "← Más reciente: %{title}"
|
|
61
|
+
older: "Anterior: %{title} →"
|
|
62
|
+
feed:
|
|
63
|
+
title_with_product: "Registro de cambios de %{product}"
|
|
64
|
+
link_title: Registro de cambios (Atom)
|
|
65
|
+
subscribe: Suscribirse (Atom)
|
|
66
|
+
admin:
|
|
67
|
+
title: Administración de Shiplog
|
|
68
|
+
nav:
|
|
69
|
+
drafts: Borradores
|
|
70
|
+
board: Tablero de la hoja de ruta
|
|
71
|
+
sync: Sincronización
|
|
72
|
+
areas: Áreas
|
|
73
|
+
areas:
|
|
74
|
+
title: Áreas del producto
|
|
75
|
+
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.
|
|
76
|
+
detect: Detectar áreas
|
|
77
|
+
recheck: Revisar de nuevo las entradas sin área
|
|
78
|
+
recheck_help: Vuelve a preguntar a la IA por las entradas que dejó sin área, por ejemplo después de renombrar, fusionar o añadir áreas.
|
|
79
|
+
no_rewriter: La detección necesita config.rewriter, que no está configurado.
|
|
80
|
+
last_detection: "Última detección: %{time}, %{count} asignadas"
|
|
81
|
+
last_error: "La última detección falló (%{time}): %{error}"
|
|
82
|
+
detection_queued: Detección en cola. Actualiza en un momento.
|
|
83
|
+
translate_names: Traducir nombres
|
|
84
|
+
translate_names_help: Pide a la IA los nombres que faltan en algún idioma. Los nombres que ya existen, incluidos los que editaste, se conservan.
|
|
85
|
+
no_locales: Traducir los nombres necesita config.locales, que está vacío.
|
|
86
|
+
no_rewriter_translate: Traducir los nombres necesita config.rewriter, que no está configurado.
|
|
87
|
+
translation_queued:
|
|
88
|
+
one: Traducción en cola para %{count} área. Actualiza en un momento.
|
|
89
|
+
other: Traducción en cola para %{count} áreas. Actualiza en un momento.
|
|
90
|
+
empty: Todavía no hay áreas. Detecta áreas para que se propongan a partir de tus entradas.
|
|
91
|
+
columns:
|
|
92
|
+
name: Nombre
|
|
93
|
+
entries: Entradas
|
|
94
|
+
order: Orden
|
|
95
|
+
merge: Fusionar con
|
|
96
|
+
name_label: Nombre
|
|
97
|
+
rename: Renombrar
|
|
98
|
+
renamed: Área renombrada.
|
|
99
|
+
move_up: Subir
|
|
100
|
+
move_down: Bajar
|
|
101
|
+
merge: Fusionar
|
|
102
|
+
merge_into: Fusionar con
|
|
103
|
+
merged: "%{from} se fusionó con %{into}."
|
|
104
|
+
merge_target: Elige otra área con la que fusionar.
|
|
105
|
+
merge_help: Al fusionar, las entradas pasan a la otra área y esta se elimina. Si eliminas un área, sus entradas se quedan sin área.
|
|
106
|
+
delete: Eliminar
|
|
107
|
+
deleted: Área eliminada. Sus entradas ya no tienen área.
|
|
108
|
+
entries:
|
|
109
|
+
title: Entradas
|
|
110
|
+
filters: Filtrar entradas
|
|
111
|
+
all_kinds: Todos los tipos
|
|
112
|
+
empty: Ninguna entrada coincide.
|
|
113
|
+
pagination: Paginación
|
|
114
|
+
older: Anteriores
|
|
115
|
+
newer: Más recientes
|
|
116
|
+
saved: Entrada guardada.
|
|
117
|
+
approved: Entrada aprobada.
|
|
118
|
+
discarded: Entrada descartada.
|
|
119
|
+
restored: Entrada devuelta a borrador.
|
|
120
|
+
invalid_transition: Esa acción no está disponible para esta entrada en su estado actual.
|
|
121
|
+
rewrite_queued: Reescritura en cola. Actualiza en un momento.
|
|
122
|
+
edit: Editar
|
|
123
|
+
edit_title: "Editar %{kind}"
|
|
124
|
+
approve: Aprobar
|
|
125
|
+
discard: Descartar
|
|
126
|
+
restore: Restaurar
|
|
127
|
+
save: Guardar
|
|
128
|
+
regenerate: Regenerar
|
|
129
|
+
no_area: Ninguna
|
|
130
|
+
area_gone: ya no existe
|
|
131
|
+
regenerate_warning: Al regenerar, esta entrada deja de estar publicada hasta que se apruebe de nuevo.
|
|
132
|
+
source_changed: El texto de GitHub cambió desde este borrador
|
|
133
|
+
rewrite_error: Falló la reescritura
|
|
134
|
+
not_rewritten: Todavía sin reescribir
|
|
135
|
+
public_text: Texto público
|
|
136
|
+
source: Origen en GitHub
|
|
137
|
+
back: Volver a los borradores
|
|
138
|
+
images:
|
|
139
|
+
title: Capturas de pantalla
|
|
140
|
+
help: Se muestran bajo el texto en el registro de cambios (la primera en la lista, todas en la página de la entrada). Se toman de la incidencia o del pull request de GitHub al escribir el borrador; también puedes subir las tuyas.
|
|
141
|
+
empty: Todavía no hay capturas.
|
|
142
|
+
alt: Texto alternativo
|
|
143
|
+
alt_placeholder: Describe la imagen (por defecto, el título)
|
|
144
|
+
save_alt: Guardar texto alternativo
|
|
145
|
+
remove: Quitar
|
|
146
|
+
move_up: Subir
|
|
147
|
+
move_down: Bajar
|
|
148
|
+
upload: Subir imágenes
|
|
149
|
+
upload_label: Imágenes (PNG, JPEG, GIF o WebP, hasta 5 MB cada una)
|
|
150
|
+
none_selected: Elige al menos una imagen.
|
|
151
|
+
rejected: "No se añadieron; solo se aceptan imágenes PNG, JPEG, GIF y WebP de hasta 5 MB y 10.000 píxeles por lado: %{names}"
|
|
152
|
+
added:
|
|
153
|
+
one: Imagen añadida.
|
|
154
|
+
other: "%{count} imágenes añadidas."
|
|
155
|
+
alt_saved: Texto alternativo guardado.
|
|
156
|
+
removed: Imagen quitada.
|
|
157
|
+
moved: Imagen movida.
|
|
158
|
+
translations:
|
|
159
|
+
title: Traducciones
|
|
160
|
+
help: "Al aprobar una entrada, esta se traduce automáticamente, y la traducción se publica junto con el texto en inglés. Una traducción que edites se conserva hasta que cambie el texto en inglés; entonces se vuelve a traducir. Una traducción desactualizada o ausente no se muestra: los visitantes ven el texto en inglés."
|
|
161
|
+
not_approved: Las traducciones se escriben cuando se aprueba la entrada.
|
|
162
|
+
missing: Falta
|
|
163
|
+
stale: Desactualizada
|
|
164
|
+
current: Al día
|
|
165
|
+
edited: Editada por un administrador
|
|
166
|
+
retranslate: Traducir de nuevo
|
|
167
|
+
saved: Traducción guardada.
|
|
168
|
+
queued: Traducción en cola. Actualiza en un momento.
|
|
169
|
+
only_approved: Solo se traducen las entradas aprobadas.
|
|
170
|
+
no_rewriter: Para traducir hace falta config.rewriter, que no está configurado.
|
|
171
|
+
category_options:
|
|
172
|
+
feature: Nueva función
|
|
173
|
+
change: Mejora
|
|
174
|
+
statuses:
|
|
175
|
+
draft: Borradores
|
|
176
|
+
approved: Aprobadas
|
|
177
|
+
discarded: Descartadas
|
|
178
|
+
kinds:
|
|
179
|
+
roadmap: Hoja de ruta
|
|
180
|
+
feature: Función
|
|
181
|
+
change: Cambio
|
|
182
|
+
columns:
|
|
183
|
+
entry: Entrada
|
|
184
|
+
kind: Tipo
|
|
185
|
+
source: Origen
|
|
186
|
+
actions: Acciones
|
|
187
|
+
fields:
|
|
188
|
+
title: Título
|
|
189
|
+
body: Texto
|
|
190
|
+
released_on: Fecha de lanzamiento
|
|
191
|
+
category: Categoría
|
|
192
|
+
area: Área
|
|
193
|
+
reason: Motivo
|
|
194
|
+
board:
|
|
195
|
+
title: Tablero de la hoja de ruta
|
|
196
|
+
help: Arrastra las tarjetas entre columnas para fijarlas y ordenarlas. Sin JavaScript, usa el botón Mover de cada tarjeta.
|
|
197
|
+
updated: Tablero actualizado.
|
|
198
|
+
not_approved: Sin aprobar
|
|
199
|
+
pinned: Fijada
|
|
200
|
+
move_to: Mover a
|
|
201
|
+
move: Mover
|
|
202
|
+
unpin: Quitar de fijadas
|
|
203
|
+
sync:
|
|
204
|
+
title: Estado de la sincronización
|
|
205
|
+
queued: Sincronización en cola.
|
|
206
|
+
webhook_secret_missing: El secreto del webhook no está configurado, así que se rechazan los webhooks de GitHub.
|
|
207
|
+
github_token_missing: El token de GitHub no está configurado, así que no se puede sincronizar.
|
|
208
|
+
sync_now: Sincronizar ahora
|
|
209
|
+
sync_and_scan: Sincronizar y buscar cambios
|
|
210
|
+
repositories: Repositorios
|
|
211
|
+
no_repositories: No hay repositorios configurados.
|
|
212
|
+
repository: Repositorio
|
|
213
|
+
synced_at: Última sincronización
|
|
214
|
+
changes_scanned_at: Cambios revisados
|
|
215
|
+
last_error: Último error
|
|
216
|
+
never: Nunca
|
|
217
|
+
deliveries: Últimas entregas de webhook
|
|
218
|
+
no_deliveries: Todavía no hay entregas.
|
|
219
|
+
event: Evento
|
|
220
|
+
action: Acción
|
|
221
|
+
received_at: Recibida
|
|
222
|
+
processed_at: Procesada
|
|
223
|
+
error: Error
|
data/config/routes.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
Shiplog::Engine.routes.draw do
|
|
2
2
|
get Shiplog.config.roadmap_path, to: "roadmaps#show", as: :roadmap
|
|
3
|
+
# The feed and the permalinks come before the plain changelog route, whose optional (.:format) would
|
|
4
|
+
# otherwise take /changelog.atom. A slug has no dot, and the permalink takes no format.
|
|
5
|
+
get "#{Shiplog.config.changelog_path}.atom", to: "changelog_feeds#show", as: :changelog_feed, defaults: { format: "atom" }
|
|
6
|
+
get "#{Shiplog.config.changelog_path}/:slug", to: "changelog_entries#show", as: :changelog_entry,
|
|
7
|
+
format: false, constraints: { slug: /[a-z0-9-]+/ }
|
|
3
8
|
get Shiplog.config.changelog_path, to: "changelogs#show", as: :changelog
|
|
4
9
|
post "shiplog/github/webhook", to: "webhooks#create", as: :github_webhook
|
|
5
10
|
get "shiplog/assets/:version/:name", to: "frontends#show", as: :frontend, constraints: { version: /[^\/]+/, name: /[a-z_]+\.(css|js)/ }
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Everything 0.3.0 adds to the schema, one section per feature. Additive and reversible.
|
|
2
|
+
class Shiplog030 < ActiveRecord::Migration[8.1]
|
|
3
|
+
# A migration must not depend on model code that changes later: this is a bare table-backed class.
|
|
4
|
+
class MigrationEntry < ActiveRecord::Base
|
|
5
|
+
self.table_name = "shiplog_entries"
|
|
6
|
+
self.inheritance_column = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
SLUG_MAX_LENGTH = 80
|
|
10
|
+
|
|
11
|
+
def change
|
|
12
|
+
# --- Entry permalinks (/changelog/:slug) ---
|
|
13
|
+
# Set when an entry is first approved, never changed afterwards, so links never break. Nil for roadmap
|
|
14
|
+
# entries and drafts. Entries approved before 0.3.0 get theirs here (oldest first, so the oldest keeps the
|
|
15
|
+
# plain slug on duplicate titles): the public pages never write, so nothing else fills them in.
|
|
16
|
+
add_column :shiplog_entries, :slug, :string
|
|
17
|
+
add_index :shiplog_entries, :slug, unique: true, where: "slug IS NOT NULL"
|
|
18
|
+
reversible do |direction|
|
|
19
|
+
direction.up do
|
|
20
|
+
MigrationEntry.reset_column_information
|
|
21
|
+
backfill_slugs
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# --- "What's new" unseen count ---
|
|
26
|
+
# `Shiplog.unseen_count` counts approved feature and change entries with `approved_at > since`:
|
|
27
|
+
# an equality column first, then the range column.
|
|
28
|
+
add_index :shiplog_entries, [ :status, :approved_at ]
|
|
29
|
+
|
|
30
|
+
# --- Product areas (detected by AI) ---
|
|
31
|
+
# `entries.area_id` is nullable; deleting an area nullifies it (merging moves the entries first).
|
|
32
|
+
# `area_set_by_admin` marks an area chosen by hand, which detection never overwrites.
|
|
33
|
+
create_table :shiplog_areas do |t|
|
|
34
|
+
t.string :name, null: false
|
|
35
|
+
t.string :slug, null: false
|
|
36
|
+
t.integer :position, null: false, default: 0
|
|
37
|
+
# The name in each of config.locales, keyed by locale string ({ "es" => "Listas de embalaje" }); empty
|
|
38
|
+
# until translated. Slugs stay English, so ?area= URLs are shared across locales.
|
|
39
|
+
t.jsonb :name_translations, null: false, default: {}
|
|
40
|
+
t.timestamps
|
|
41
|
+
end
|
|
42
|
+
add_index :shiplog_areas, :slug, unique: true
|
|
43
|
+
add_reference :shiplog_entries, :area, null: true, index: true, foreign_key: { to_table: :shiplog_areas, on_delete: :nullify }
|
|
44
|
+
add_column :shiplog_entries, :area_set_by_admin, :boolean, null: false, default: false
|
|
45
|
+
# When detection last asked the AI about the entry (placed or not): unplaced entries are not asked again
|
|
46
|
+
# until an admin asks for a re-check.
|
|
47
|
+
add_column :shiplog_entries, :area_detected_at, :datetime
|
|
48
|
+
# A small key-value store for state that belongs to no record, e.g. the result of the last area detection
|
|
49
|
+
# (shown on the admin's Areas page).
|
|
50
|
+
create_table :shiplog_settings do |t|
|
|
51
|
+
t.string :key, null: false
|
|
52
|
+
t.jsonb :value, null: false, default: {}
|
|
53
|
+
t.timestamps
|
|
54
|
+
end
|
|
55
|
+
add_index :shiplog_settings, :key, unique: true
|
|
56
|
+
|
|
57
|
+
# --- Screenshots (needs Active Storage in the host; the columns are harmless without it) ---
|
|
58
|
+
# `image_order` is the ordered list of the entry's image blob ids, `image_alts` the alt text per blob id
|
|
59
|
+
# ({ "12" => "The dark theme" }); a missing alt text falls back to the entry's title.
|
|
60
|
+
add_column :shiplog_entries, :image_order, :jsonb, null: false, default: []
|
|
61
|
+
add_column :shiplog_entries, :image_alts, :jsonb, null: false, default: {}
|
|
62
|
+
|
|
63
|
+
# --- Translations of entries (config.locales) ---
|
|
64
|
+
# One row per entry and locale, published with the English text (no second approval). `source_digest` is the
|
|
65
|
+
# digest of the English title and body it was made from: a row whose digest differs from the entry's current
|
|
66
|
+
# text is stale and never shown. `edited_by_admin` keeps an admin's edit until the English text changes.
|
|
67
|
+
create_table :shiplog_entry_translations do |t|
|
|
68
|
+
t.references :entry, null: false, index: false, foreign_key: { to_table: :shiplog_entries, on_delete: :cascade }
|
|
69
|
+
t.string :locale, null: false
|
|
70
|
+
t.string :title, null: false
|
|
71
|
+
t.text :body, null: false, default: ""
|
|
72
|
+
t.string :source_digest, null: false
|
|
73
|
+
t.boolean :edited_by_admin, null: false, default: false
|
|
74
|
+
t.timestamps
|
|
75
|
+
end
|
|
76
|
+
add_index :shiplog_entry_translations, [ :entry_id, :locale ], unique: true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Gives every approved feature and change with a release date, and no slug, one: the title as [a-z0-9-] only
|
|
80
|
+
# (cut at 80 characters), made unique with -2, -3, ...; entry-<id> when nothing is left. The same rules as
|
|
81
|
+
# Shiplog::Entry#generate_slug at the time of 0.3.0.
|
|
82
|
+
def backfill_slugs
|
|
83
|
+
taken = MigrationEntry.where.not(slug: nil).pluck(:slug).to_set
|
|
84
|
+
MigrationEntry.where(status: "approved", kind: %w[feature change], slug: nil).where.not(released_on: nil)
|
|
85
|
+
.order(:released_on, :approved_at, :id).to_a.each do |entry|
|
|
86
|
+
slug = slug_for(entry, taken)
|
|
87
|
+
taken << slug
|
|
88
|
+
MigrationEntry.where(id: entry.id, slug: nil).update_all(slug:)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def slug_for(entry, taken)
|
|
95
|
+
base = entry.title.to_s.parameterize.tr("_", "-").squeeze("-").first(SLUG_MAX_LENGTH).gsub(/\A-+|-+\z/, "").presence || "entry-#{entry.id}"
|
|
96
|
+
candidate = base
|
|
97
|
+
suffix = 2
|
|
98
|
+
while taken.include?(candidate)
|
|
99
|
+
tail = "-#{suffix}"
|
|
100
|
+
candidate = "#{base.first(SLUG_MAX_LENGTH - tail.length).delete_suffix("-")}#{tail}"
|
|
101
|
+
suffix += 1
|
|
102
|
+
end
|
|
103
|
+
candidate
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -59,7 +59,16 @@ module Shiplog
|
|
|
59
59
|
search_button: "rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-indigo-500",
|
|
60
60
|
search_summary: "mb-4 text-sm text-gray-500 dark:text-gray-400",
|
|
61
61
|
search_clear: "ml-2 font-medium text-indigo-600 hover:underline dark:text-indigo-400",
|
|
62
|
-
search_empty: "text-gray-500 dark:text-gray-400"
|
|
62
|
+
search_empty: "text-gray-500 dark:text-gray-400",
|
|
63
|
+
permalink: "mx-auto max-w-2xl rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900",
|
|
64
|
+
permalink_back: "mt-6 inline-block text-sm font-medium text-indigo-600 hover:underline dark:text-indigo-400",
|
|
65
|
+
permalink_nav: "mt-8 flex flex-wrap justify-between gap-4 border-t border-gray-200 pt-4 dark:border-gray-800",
|
|
66
|
+
permalink_nav_link: "text-sm font-medium text-indigo-600 hover:underline dark:text-indigo-400",
|
|
67
|
+
areas: "my-4 flex flex-wrap gap-2",
|
|
68
|
+
area_link: "rounded-full border border-gray-300 px-3 py-0.5 text-sm hover:border-indigo-500 dark:border-gray-700 [&.shiplog-areas__link--current]:border-indigo-600 [&.shiplog-areas__link--current]:bg-indigo-600 [&.shiplog-areas__link--current]:text-white",
|
|
69
|
+
entry_area: "text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400",
|
|
70
|
+
entry_images: "mt-3 grid w-full gap-3",
|
|
71
|
+
entry_image: "h-auto max-w-full rounded-lg border border-gray-200 dark:border-gray-800"
|
|
63
72
|
}.freeze
|
|
64
73
|
|
|
65
74
|
def copy_initializer
|
|
@@ -97,7 +97,12 @@ Shiplog.configure do |config|
|
|
|
97
97
|
|
|
98
98
|
# Public paths, read when routes are drawn.
|
|
99
99
|
# config.roadmap_path = "roadmap"
|
|
100
|
-
# config.changelog_path = "changelog"
|
|
100
|
+
# config.changelog_path = "changelog" # a-z, 0-9, - and _; "docs/news" nests; outer slashes are stripped
|
|
101
|
+
|
|
102
|
+
# The site's canonical origin (scheme, host, optional port; no path). The Atom feed's ids and links, og:url
|
|
103
|
+
# and the other absolute URLs use it instead of the request's host, which varies (apex vs www, a forged
|
|
104
|
+
# X-Forwarded-Host). Behind a proxy, also set config.assume_ssl = true (or send X-Forwarded-Proto) in Rails.
|
|
105
|
+
# config.canonical_url = "https://example.com"
|
|
101
106
|
|
|
102
107
|
# Pages added to the Roadmap / Changelog switcher. label and path are Strings, or callables evaluated in the
|
|
103
108
|
# view (route helpers, I18n). Render the same switcher on your own pages with `helper Shiplog::SwitcherHelper`
|
|
@@ -117,6 +122,58 @@ Shiplog.configure do |config|
|
|
|
117
122
|
# The public changelog's search box and ?q= filtering. Set to false to turn search off.
|
|
118
123
|
# config.changelog_search = true
|
|
119
124
|
|
|
125
|
+
# schema.org structured data (JSON-LD) on the public pages: an Article on each permalink, a CollectionPage on
|
|
126
|
+
# the changelog and the roadmap. Set to false to turn it off.
|
|
127
|
+
# config.structured_data = true
|
|
128
|
+
|
|
129
|
+
# "What's new". Shiplog doesn't know your users, so you supply the "last seen" time. This is called with the
|
|
130
|
+
# controller on every changelog index or permalink view, BEFORE the page renders (a 304 revisit included), so a
|
|
131
|
+
# navbar badge in the same response is current. It may write and runs on every visit: keep it cheap, and write
|
|
132
|
+
# only when something is new. Show `Shiplog.unseen_count(since: user.changelog_seen_at)` (at most 99, counted on
|
|
133
|
+
# approved_at; `helper Shiplog::UnseenHelper` and `shiplog_unseen_count(since)` in views) next to your Changelog
|
|
134
|
+
# link. current_user is often private, hence controller.send (or read a Current attribute).
|
|
135
|
+
# config.on_changelog_seen = lambda do |controller|
|
|
136
|
+
# user = controller.send(:current_user)
|
|
137
|
+
# user.update_column(:changelog_seen_at, Time.current) if user && Shiplog.unseen_count(since: user.changelog_seen_at).positive?
|
|
138
|
+
# end
|
|
139
|
+
|
|
140
|
+
# The 0.3.0 migration gives entries approved so far their permalink slug. The public pages never write (a GET
|
|
141
|
+
# may hit a read replica), so an entry approved by older code during a deploy window has no permalink until
|
|
142
|
+
# `bin/rails shiplog:backfill_slugs` runs.
|
|
143
|
+
|
|
144
|
+
# With your own config.public_layout, add `<%%= shiplog_head_tags %%>` to its <head>: it renders the description,
|
|
145
|
+
# canonical link, robots, Open Graph, Twitter, Atom discovery and JSON-LD tags of the Shiplog pages (and nothing
|
|
146
|
+
# on your other pages). `except: [:feed, ...]` leaves out what your layout writes itself. The older
|
|
147
|
+
# `content_for :shiplog_og`, `:shiplog_description`, `:shiplog_feed`, `:shiplog_robots` and
|
|
148
|
+
# `:shiplog_structured_data` blocks still work but are superseded: use one or the other, never both.
|
|
149
|
+
|
|
150
|
+
# The image shared with a permalink page that has no screenshot (og:image, twitter:image, JSON-LD image): a
|
|
151
|
+
# callable evaluated in the view, so route helpers work. Return an http(s) URL or a path (made absolute); an
|
|
152
|
+
# error or any other value is logged and the page gets no image. List pages and the roadmap get none from Shiplog.
|
|
153
|
+
# config.og_image = ->(entry, locale) { main_app.url_for(controller: "/og_images", action: :show, id: entry.slug) }
|
|
154
|
+
|
|
155
|
+
# Product areas. The rewriter's reply may name an area (an existing one, or a new name of 1-3 words); Shiplog
|
|
156
|
+
# creates it while fewer than max_areas exist, so the AI cannot sprawl. The changelog offers an area filter
|
|
157
|
+
# (?area=slug) once two areas have published entries. Manage areas in the admin ("Areas"): rename, reorder,
|
|
158
|
+
# merge, delete, and "Detect areas" to backfill entries that have none (it reuses config.rewriter).
|
|
159
|
+
# Entries you set by hand on the edit page are never changed by the AI.
|
|
160
|
+
# config.max_areas = 8
|
|
161
|
+
|
|
162
|
+
# Screenshots in entries. They need Active Storage in your app (require "active_storage/engine", and its tables);
|
|
163
|
+
# without it they stay off and Shiplog logs that once at boot. When a draft is written, the images in the GitHub
|
|
164
|
+
# issue or pull request body are attached to the entry (GitHub image hosts only, 5 MB and PNG, JPEG, GIF or
|
|
165
|
+
# WebP at most; the token needs no extra permission). You can also upload images on the entry edit page.
|
|
166
|
+
# config.images = true
|
|
167
|
+
# config.max_images_per_entry = 4
|
|
168
|
+
|
|
169
|
+
# Translations of entries. Entries are translated into each of these locales with config.rewriter when they are
|
|
170
|
+
# approved, and again when the English title or body of an approved entry changes; the translation is published
|
|
171
|
+
# at once, with no second approval. One LLM call per entry, locale and change. Admins can edit a translation on
|
|
172
|
+
# the entry page (kept until the English text changes) or retranslate it. Pages render the translation when
|
|
173
|
+
# I18n.locale is one of these and it matches the current English text, and English otherwise. Your routes
|
|
174
|
+
# decide which URLs set the locale (e.g. a /es scope). The default locale is dropped from the list.
|
|
175
|
+
# config.locales = [] # e.g. [:es]
|
|
176
|
+
|
|
120
177
|
# Callables above cannot be part of the public pages' ETag. Bump this when the output of a callable you
|
|
121
178
|
# configured changes (a new label, another path) so cached pages are refreshed.
|
|
122
179
|
# config.cache_version = nil
|
data/lib/shiplog/classes.rb
CHANGED
|
@@ -49,7 +49,16 @@ module Shiplog
|
|
|
49
49
|
search_button: "shiplog-search__button",
|
|
50
50
|
search_summary: "shiplog-search__summary",
|
|
51
51
|
search_clear: "shiplog-search__clear",
|
|
52
|
-
search_empty: "shiplog-search__empty"
|
|
52
|
+
search_empty: "shiplog-search__empty",
|
|
53
|
+
permalink: "shiplog-permalink",
|
|
54
|
+
permalink_back: "shiplog-permalink__back",
|
|
55
|
+
permalink_nav: "shiplog-permalink__nav",
|
|
56
|
+
permalink_nav_link: "shiplog-permalink__nav-link",
|
|
57
|
+
areas: "shiplog-areas",
|
|
58
|
+
area_link: "shiplog-areas__link",
|
|
59
|
+
entry_area: "shiplog-entry__area",
|
|
60
|
+
entry_images: "shiplog-entry__images",
|
|
61
|
+
entry_image: "shiplog-entry__image"
|
|
53
62
|
}.freeze
|
|
54
63
|
|
|
55
64
|
KEYS = BEM.keys.freeze
|
|
@@ -5,7 +5,7 @@ module Shiplog
|
|
|
5
5
|
:rewriter, :product_name, :audience, :tone, :prompt_builder,
|
|
6
6
|
:public_layout, :admin_layout,
|
|
7
7
|
:stylesheet, :admin_flash, :authorize_admin, :current_admin,
|
|
8
|
-
:roadmap_path
|
|
8
|
+
:roadmap_path
|
|
9
9
|
|
|
10
10
|
ADMIN_CHROMES = %i[standalone hosted].freeze
|
|
11
11
|
# Segments of only dots ("." and "..") are path traversal, not names.
|
|
@@ -16,16 +16,47 @@ module Shiplog
|
|
|
16
16
|
# Path of the lockfile read in merged pull requests to detect gem upgrades (see config.repositories `gem:`).
|
|
17
17
|
attr_accessor :lockfile_path
|
|
18
18
|
|
|
19
|
+
# The public changelog's path: segments of a-z, 0-9, dashes and underscores, joined by slashes ("changelog",
|
|
20
|
+
# "docs/whats_new"); surrounding slashes are stripped. Permalinks live below it and the Atom feed is
|
|
21
|
+
# "<path>.atom". Read when routes are drawn.
|
|
22
|
+
attr_reader :changelog_path
|
|
23
|
+
CHANGELOG_PATH = %r{\A[A-Za-z0-9_-]+(?:/[A-Za-z0-9_-]+)*\z}
|
|
24
|
+
|
|
25
|
+
# The site's canonical origin, e.g. "https://trackberry.com" (nil by default). When set, the Atom feed's ids
|
|
26
|
+
# and links, entry links, og:url and the other absolute URLs use it instead of the request's host, which
|
|
27
|
+
# varies (apex and www, a forged X-Forwarded-Host). An origin only: scheme, host and optional port. Any path
|
|
28
|
+
# prefix of the engine's mount comes from the routes, so it needs no place here.
|
|
29
|
+
attr_reader :canonical_url
|
|
30
|
+
|
|
19
31
|
attr_reader :admin_chrome, :classes, :public_parent_controller, :admin_parent_controller
|
|
20
32
|
|
|
21
33
|
# Product updates navigation and roadmap options.
|
|
22
34
|
attr_accessor :feedback_path
|
|
23
35
|
# Whether the public changelog has a search box and honours ?q= (default true).
|
|
24
36
|
attr_accessor :changelog_search
|
|
37
|
+
# Whether the public pages carry schema.org JSON-LD (rendered by shiplog_head_tags; default true).
|
|
38
|
+
attr_accessor :structured_data
|
|
39
|
+
# The image shared with a permalink page when the entry has no screenshot: nil (default) or a callable
|
|
40
|
+
# `->(entry, locale) { "https://cdn.example.com/card.png" }` evaluated in the view (instance_exec, so
|
|
41
|
+
# `main_app` route helpers work). A path is made absolute with canonical_url or the request's origin; an
|
|
42
|
+
# error or a non-http(s) value is logged and the page renders without the image. See shiplog_head_tags.
|
|
43
|
+
attr_reader :og_image
|
|
44
|
+
# Called with the controller on each changelog index or permalink view (200 or 304 answers), e.g. to stamp
|
|
45
|
+
# the visitor's "last seen" time. It runs BEFORE the page renders (so a badge in the same response is
|
|
46
|
+
# current), on every visit, and may write: keep it cheap, e.g. write only when the stored time is older than
|
|
47
|
+
# the newest entry. Nil by default. Not part of the ETag: it doesn't change the markup.
|
|
48
|
+
attr_reader :on_changelog_seen
|
|
25
49
|
# Bump it when the output of a callable you configured changes (extra_pages, repository_labels, feedback_path):
|
|
26
50
|
# callables cannot be part of the ETag, so this is how cached public pages learn about it.
|
|
27
51
|
attr_accessor :cache_version
|
|
28
52
|
attr_reader :extra_pages, :repository_labels, :roadmap_shipped_count
|
|
53
|
+
# How many product areas the AI may create (default 8). Past it, entries can only join existing areas.
|
|
54
|
+
# Not part of render_digest: the markup depends on the areas themselves, which the ETag carries.
|
|
55
|
+
attr_reader :max_areas
|
|
56
|
+
# Screenshots in entries. `images` (default true) is effective only when Active Storage is loaded in the host
|
|
57
|
+
# (see Shiplog.images?). `max_images_per_entry` (default 4) caps the images fetched from GitHub per entry.
|
|
58
|
+
attr_reader :images, :max_images_per_entry
|
|
59
|
+
LOCALE_FORMAT = /\A[A-Za-z][A-Za-z0-9_-]*\z/
|
|
29
60
|
|
|
30
61
|
EXTRA_PAGE_KEYS = %i[label path].freeze
|
|
31
62
|
|
|
@@ -43,10 +74,10 @@ module Shiplog
|
|
|
43
74
|
stable = ->(value) { value.respond_to?(:call) ? "callable" : value }
|
|
44
75
|
data = [
|
|
45
76
|
classes.sort_by { |key, _| key.to_s }.map { |key, value| [ key.to_s, Array(value).map(&:to_s) ] },
|
|
46
|
-
stylesheet, roadmap_shipped_count, cache_version,
|
|
77
|
+
stylesheet, roadmap_shipped_count, cache_version, product_name, canonical_url,
|
|
47
78
|
extra_pages.map { |page| [ stable.(page[:label]), stable.(page[:path]) ] },
|
|
48
79
|
repository_labels.sort_by { |name, _| name.to_s }.map { |name, label| [ name, stable.(label) ] },
|
|
49
|
-
stable.(feedback_path), changelog_search
|
|
80
|
+
stable.(feedback_path), changelog_search, images, max_images_per_entry, locales, structured_data, stable.(og_image)
|
|
50
81
|
]
|
|
51
82
|
Digest::SHA256.hexdigest(JSON.generate(data))
|
|
52
83
|
end
|
|
@@ -92,6 +123,56 @@ module Shiplog
|
|
|
92
123
|
@roadmap_shipped_count = value
|
|
93
124
|
end
|
|
94
125
|
|
|
126
|
+
def max_areas=(value)
|
|
127
|
+
raise ArgumentError, "max_areas must be a non-negative Integer, got #{value.inspect}" unless value.is_a?(Integer) && value >= 0
|
|
128
|
+
@max_areas = value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def images=(value)
|
|
132
|
+
raise ArgumentError, "images must be true or false, got #{value.inspect}" unless [ true, false ].include?(value)
|
|
133
|
+
@images = value
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def max_images_per_entry=(value)
|
|
137
|
+
raise ArgumentError, "max_images_per_entry must be a non-negative Integer, got #{value.inspect}" unless value.is_a?(Integer) && value >= 0
|
|
138
|
+
@max_images_per_entry = value
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Locales entries are translated into when approved, e.g. [:es] (default none), as symbols and without the
|
|
142
|
+
# default locale (I18n.default_locale, read each time so an initializer that sets it later still counts): it
|
|
143
|
+
# is the language entries are written in. See Shiplog::Translator.
|
|
144
|
+
def locales = @locales - [ I18n.default_locale.to_sym ]
|
|
145
|
+
|
|
146
|
+
# Sets the locales to translate into: an Array of locale Symbols or Strings (see #locales).
|
|
147
|
+
def locales=(value)
|
|
148
|
+
unless value.is_a?(Array) && value.all? { |item| (item.is_a?(Symbol) || item.is_a?(String)) && LOCALE_FORMAT.match?(item.to_s) }
|
|
149
|
+
raise ArgumentError, "locales must be an Array of locale Symbols or Strings like [:es, \"pt-BR\"], got #{value.inspect}"
|
|
150
|
+
end
|
|
151
|
+
@locales = value.map(&:to_sym).uniq.freeze
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def changelog_path=(value)
|
|
155
|
+
path = value.gsub(%r{\A/+|/+\z}, "") if value.is_a?(String)
|
|
156
|
+
unless path && CHANGELOG_PATH.match?(path)
|
|
157
|
+
raise ArgumentError, "changelog_path must be a path of letters, digits, dashes and underscores, with / between segments (like \"changelog\"), got #{value.inspect}"
|
|
158
|
+
end
|
|
159
|
+
@changelog_path = path
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def canonical_url=(value)
|
|
163
|
+
@canonical_url = normalize_canonical_url(value)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def og_image=(value)
|
|
167
|
+
raise ArgumentError, "og_image must be nil or a callable like ->(entry, locale) { url }, got #{value.inspect}" unless value.nil? || value.respond_to?(:call)
|
|
168
|
+
@og_image = value
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def on_changelog_seen=(value)
|
|
172
|
+
raise ArgumentError, "on_changelog_seen must be nil or a callable, got #{value.inspect}" unless value.nil? || value.respond_to?(:call)
|
|
173
|
+
@on_changelog_seen = value
|
|
174
|
+
end
|
|
175
|
+
|
|
95
176
|
# :standalone (default) wraps the admin in Shiplog's own page chrome; :hosted renders it inside the
|
|
96
177
|
# layout of config.admin_layout (compact sub-nav, no page chrome of its own).
|
|
97
178
|
def admin_chrome=(value)
|
|
@@ -158,6 +239,11 @@ module Shiplog
|
|
|
158
239
|
@repository_labels = {}.freeze
|
|
159
240
|
@roadmap_shipped_count = 4
|
|
160
241
|
@changelog_search = true
|
|
242
|
+
@structured_data = true
|
|
243
|
+
@max_areas = 8
|
|
244
|
+
@images = true
|
|
245
|
+
@max_images_per_entry = 4
|
|
246
|
+
@locales = [].freeze
|
|
161
247
|
end
|
|
162
248
|
|
|
163
249
|
def resolved_github_token
|
|
@@ -196,6 +282,22 @@ module Shiplog
|
|
|
196
282
|
|
|
197
283
|
private
|
|
198
284
|
|
|
285
|
+
def normalize_canonical_url(value)
|
|
286
|
+
return if value.nil? || (value.is_a?(String) && value.strip.empty?)
|
|
287
|
+
raise ArgumentError, "canonical_url must be a String like \"https://example.com\", got #{value.inspect}" unless value.is_a?(String)
|
|
288
|
+
|
|
289
|
+
uri = begin
|
|
290
|
+
URI.parse(value.strip)
|
|
291
|
+
rescue URI::InvalidURIError
|
|
292
|
+
nil
|
|
293
|
+
end
|
|
294
|
+
valid = uri.is_a?(URI::HTTP) && uri.host.present? && [ "", "/" ].include?(uri.path) && uri.query.nil? && uri.fragment.nil? && uri.userinfo.nil?
|
|
295
|
+
raise ArgumentError, "canonical_url must be an http(s) origin without path, query or credentials (\"https://example.com\"), got #{value.inspect}" unless valid
|
|
296
|
+
|
|
297
|
+
port = ":#{uri.port}" unless uri.port == uri.default_port
|
|
298
|
+
"#{uri.scheme}://#{uri.host.downcase}#{port}"
|
|
299
|
+
end
|
|
300
|
+
|
|
199
301
|
def normalize_repositories(value)
|
|
200
302
|
raise ArgumentError, "config.repositories must be an Array, got #{value.inspect}" unless value.is_a?(Array)
|
|
201
303
|
|
data/lib/shiplog/engine.rb
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
module Shiplog
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
isolate_namespace Shiplog
|
|
4
|
+
|
|
5
|
+
# shiplog_head_tags in every view, so a layout shared with the host's own pages can call it.
|
|
6
|
+
initializer "shiplog.head_tags_helper" do
|
|
7
|
+
ActiveSupport.on_load(:action_view) { include Shiplog::HeadTagsHelper }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Once per boot, after the host's initializers have set config.images: say why screenshots are off.
|
|
11
|
+
config.after_initialize { Shiplog.log_images_status }
|
|
4
12
|
end
|
|
5
13
|
end
|
|
@@ -133,7 +133,19 @@ module Shiplog
|
|
|
133
133
|
}
|
|
134
134
|
GRAPHQL
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
# The rendered HTML of one issue or pull request body (`$id` is its node id). Kept out of the sync documents so
|
|
137
|
+
# their payloads don't grow: it is only asked for when a draft is written, to find screenshots. On private
|
|
138
|
+
# repositories its <img src> URLs are short-lived signed URLs.
|
|
139
|
+
BODY_HTML = <<~GRAPHQL.freeze
|
|
140
|
+
query($id: ID!) {
|
|
141
|
+
node(id: $id) {
|
|
142
|
+
... on Issue { bodyHTML }
|
|
143
|
+
... on PullRequest { bodyHTML }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
GRAPHQL
|
|
147
|
+
|
|
148
|
+
ALL = { ISSUES:, PULL_REQUESTS:, ISSUE:, PULL_REQUEST:, COMMITS:, BLOB_TEXT:, COMMIT_PULL_REQUESTS:, BODY_HTML: }.freeze
|
|
137
149
|
end
|
|
138
150
|
end
|
|
139
151
|
end
|
data/lib/shiplog/version.rb
CHANGED