product_tours 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bca51e6535dbdd38d6d683ee0ea95e430acbefd41479f356e1c0e7c330c64697
4
- data.tar.gz: fce14b868cabf23946d5658aa5c9362d5b0e3df92e326b27f31164b9590e01cd
3
+ metadata.gz: 397e2fbd273b707e9a16302852704ed8e5cc6daf410f3255fe3fa819047163b8
4
+ data.tar.gz: c040cac638d52a7484f4ef109732211e181f0b103bdbcd938f0d6c3c7b8d10e1
5
5
  SHA512:
6
- metadata.gz: 1dec9e04d694dd26b2baafbe9eb6226370ccb728c500c086ebc387e8cf6bdfa7433f5588772b9a9e61be26e2d5558af59dd4d096424a429cc9004abed73b3084
7
- data.tar.gz: b86e2d428a86bb9708cbb709eb80e0e5de1ead8bf3a18d32e78c38d64a9e24ce7d41a2bbaea3ee7d214e8d4dadecc74bc35dce1ffcd74e570eb7a78ea3095281
6
+ metadata.gz: b22e2391759e9a49d90728b9709650e5f0f2d1001fdf36d71e4c8d2c494e853007f70794b93c92762363502872f024970cf62660fa4639702aac82c854b2f9ab
7
+ data.tar.gz: e2ceef66890c00c8a5f3f9293f946e35c366215b08a584a52e49408cb858f245affe2eab62caece0f1b7286f5b5944da9358f3315777b95208e962e715b83b2f
data/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.3] - 2026-08-08
6
+
7
+ - **The demo walkthrough is now a tour of Product Tours itself.** Its linked
8
+ steps explain host-owned triggers, post chaining, optional video, drafts,
9
+ locale-stable keys, lifecycle events, direct and uploaded video delivery, and
10
+ completion. The standalone buttons still exercise every provider, a draft,
11
+ a missing key, and an action URL, while making the purpose of each case clear.
12
+ - English, French, and Bulgarian demo posts carry the educational copy. Rich
13
+ descriptions are added when Action Text is installed; title-only installs
14
+ still get the guided sequence. The provider videos remain generic examples.
15
+ - `product_tours:seed_demo` preserves demo keys and refreshes their content, so
16
+ rerunning it overwrites edits to those gem-owned posts. Tutorials with other
17
+ keys are not touched.
18
+
19
+ ## [0.3.2] - 2026-08-08
20
+
21
+ - **Video tutorials are comfortably watchable without entering full screen.**
22
+ Text-only dialogs remain compact at 440px, while a tutorial containing video
23
+ automatically expands to 760px on desktop. Once a video appears in an open
24
+ walkthrough, the dialog stays wide for its remaining steps so the layout does
25
+ not jump back and forth. Mobile dialogs remain full-screen. There is no new
26
+ configuration or API to adopt.
27
+
5
28
  ## [0.3.1] - 2026-08-08
6
29
 
7
30
  - **Hosts can handle lifecycle events with their own request context.** Set
@@ -133,7 +156,8 @@
133
156
  - Removed generic tutorial duplication now that translations provide the only
134
157
  intentional content-copying workflow.
135
158
 
136
- [Unreleased]: https://github.com/yshmarov/product_tours/compare/v0.3.1...HEAD
159
+ [Unreleased]: https://github.com/yshmarov/product_tours/compare/v0.3.2...HEAD
160
+ [0.3.2]: https://github.com/yshmarov/product_tours/compare/v0.3.1...v0.3.2
137
161
  [0.3.1]: https://github.com/yshmarov/product_tours/compare/v0.3.0...v0.3.1
138
162
  [0.1.1]: https://github.com/yshmarov/product_tours/compare/v0.1.0...v0.1.1
139
163
  [0.1.0]: https://github.com/yshmarov/product_tours/releases/tag/v0.1.0
data/README.md CHANGED
@@ -92,8 +92,11 @@ the things it should not do. It ships inside the gem, so
92
92
 
93
93
  <img src="docs/screenshots/03-mobile.jpg" alt="The product tutorial modal filling a mobile viewport with a video and primary action" width="390">
94
94
 
95
- On screens up to 480px the modal becomes a full-screen sheet, respects safe
96
- areas, and follows `visualViewport` while the mobile keyboard is open.
95
+ Text-only tutorials stay compact at 440px on desktop. A tutorial with video
96
+ automatically opens at up to 760px, and a walkthrough stays at that size after
97
+ its first video so the layout does not jump between steps. On screens up to
98
+ 480px the modal becomes a full-screen sheet, respects safe areas, and follows
99
+ `visualViewport` while the mobile keyboard is open.
97
100
 
98
101
  ## Why a gem
99
102
 
@@ -7,107 +7,221 @@ module ProductTours
7
7
  POSTS = [
8
8
  {
9
9
  key: 'demo_walkthrough_finish',
10
- title: 'You are ready',
11
- action_label: 'Get started'
10
+ title: 'You completed the Product Tours tour',
11
+ description: '<p>Completing this step emits <code>product_tours.completed</code>. ' \
12
+ 'Replace these demo posts with one short guide tied to a real customer success moment.</p>',
13
+ action_label: 'Finish tour'
12
14
  },
13
15
  {
14
16
  key: 'demo_direct_video',
15
- title: 'Direct MP4 tutorial',
17
+ title: 'Keep video delivery flexible',
18
+ description: '<p>This placeholder uses a direct MP4. You can also attach a video with Active Storage; ' \
19
+ 'uploaded media streams through the engine rather than a public blob URL.</p>',
16
20
  video_url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
17
21
  action_post_key: 'demo_walkthrough_finish'
18
22
  },
19
23
  {
20
24
  key: 'demo_voomly',
21
- title: 'Voomly tutorial',
25
+ title: 'Observe the lifecycle',
26
+ description: '<p>Subscribe to viewed, dismissed, completed, and unresolved-trigger notifications, ' \
27
+ 'or use <code>config.on_event</code> when you need the raw request to resolve a user.</p>',
22
28
  video_url: 'https://share.voomly.com/v/CxfDyNYKE0SBEBV-zVAUHo8rNwS2eLBr418z81gd6GlkKlLfJ',
23
29
  action_post_key: 'demo_direct_video'
24
30
  },
25
31
  {
26
32
  key: 'demo_tella',
27
- title: 'Tella tutorial',
33
+ title: 'Translate one stable key',
34
+ description: '<p>A tutorial keeps the same key in every locale. Product Tours resolves the visitor locale ' \
35
+ 'and falls back to your default locale, so triggers never need translated keys.</p>',
28
36
  video_url: 'https://www.tella.tv/video/add-your-logo-to-videos-39y0',
29
37
  action_post_key: 'demo_voomly'
30
38
  },
31
39
  {
32
40
  key: 'demo_loom',
33
- title: 'Loom tutorial',
41
+ title: 'Draft safely, then publish',
42
+ description: '<p>The seeded <code>demo_draft</code> post appears in the dashboard but cannot open for ' \
43
+ 'visitors. Use drafts to review copy, actions, and video before a trigger goes live.</p>',
34
44
  video_url: 'https://www.loom.com/share/e5b8c04bca094dd8a5507925ab887002',
35
45
  action_post_key: 'demo_tella'
36
46
  },
37
47
  {
38
48
  key: 'demo_vimeo',
39
- title: 'Vimeo tutorial',
49
+ title: 'Chain focused steps',
50
+ description: '<p>This Next button follows <code>action_post_key</code>. Each step is an ordinary tutorial, ' \
51
+ 'so you can reuse it alone or connect it into a walkthrough without host-side JavaScript.</p>',
40
52
  video_url: 'https://vimeo.com/76979871',
41
53
  action_post_key: 'demo_loom'
42
54
  },
43
55
  {
44
56
  key: 'demo_youtube',
45
- title: 'YouTube tutorial',
57
+ title: 'Add video only when it helps',
58
+ description: '<p>This generic YouTube clip is only a provider example. Replace it with your own explanation, ' \
59
+ 'or leave video blank for a fast text-only guide.</p>',
46
60
  video_url: 'https://www.youtube.com/watch?v=M7lc1UVf-VE',
47
61
  action_post_key: 'demo_vimeo'
48
62
  },
49
63
  {
50
64
  key: 'demo_walkthrough_features',
51
- title: 'Compare every supported video provider',
65
+ title: 'Trigger a tour from your own UI',
66
+ description: '<p>Add <code>data-product-tour="demo_walkthrough_start"</code> to any button or link. ' \
67
+ 'The single <code>product_tours_tag</code> in your layout handles every trigger on the page.</p>',
52
68
  action_post_key: 'demo_youtube'
53
69
  },
54
70
  {
55
71
  key: 'demo_walkthrough_start',
56
- title: 'Video provider walkthrough',
72
+ title: 'Welcome to Product Tours',
73
+ description: '<p>This is a real multi-step tour created by <code>product_tours:seed_demo</code>. ' \
74
+ 'Continue to learn the public API by using the same experience your customers will see.</p>',
57
75
  action_post_key: 'demo_walkthrough_features'
58
76
  },
59
77
  {
60
78
  key: 'demo_getting_started',
61
- title: 'Getting started',
62
- action_label: 'Open settings',
79
+ title: 'Link to the next real action',
80
+ description: '<p>An action URL completes the tutorial and sends the visitor into your product. ' \
81
+ 'Point it at the screen where they can immediately use what the guide taught.</p>',
82
+ action_label: 'Try the action URL',
63
83
  action_url: '/settings'
64
84
  },
65
85
  {
66
86
  key: 'demo_draft',
67
- title: 'Draft tutorial',
87
+ title: 'This draft stays invisible to visitors',
88
+ description: '<p>You can edit and preview this post in the dashboard, but its demo trigger will not resolve ' \
89
+ 'until you publish it.</p>',
68
90
  status: 'draft'
69
91
  }
70
92
  ].freeze
71
93
 
72
94
  BUTTONS = [
73
- ['demo_walkthrough_start', 'Try the multi-step walkthrough'],
74
- ['demo_youtube', 'Open the YouTube tutorial'],
75
- ['demo_vimeo', 'Open the Vimeo tutorial'],
76
- ['demo_loom', 'Open the Loom tutorial'],
77
- ['demo_tella', 'Open the Tella tutorial'],
78
- ['demo_voomly', 'Open the Voomly tutorial'],
79
- ['demo_direct_video', 'Open the direct video tutorial'],
80
- ['demo_getting_started', 'Open the getting started guide'],
81
- ['demo_draft', 'Try an unpublished tutorial'],
82
- ['demo_missing_post', 'Try a missing tutorial']
95
+ ['demo_walkthrough_start', 'Take the self-guided Product Tours tour'],
96
+ ['demo_youtube', 'Preview a YouTube video step'],
97
+ ['demo_vimeo', 'Preview a Vimeo video step'],
98
+ ['demo_loom', 'Preview a Loom video step'],
99
+ ['demo_tella', 'Preview a Tella video step'],
100
+ ['demo_voomly', 'Preview a Voomly video step'],
101
+ ['demo_direct_video', 'Preview a direct MP4 step'],
102
+ ['demo_getting_started', 'Try a tour that opens a product page'],
103
+ ['demo_draft', 'Confirm that draft tours stay hidden'],
104
+ ['demo_missing_post', 'Confirm that missing keys are instrumented']
83
105
  ].freeze
84
106
 
85
107
  TRANSLATIONS = {
86
108
  'fr' => {
87
- 'demo_walkthrough_finish' => { title: 'Vous êtes prêt', action_label: 'Commencer' },
88
- 'demo_walkthrough_features' => { title: 'Comparez tous les fournisseurs vidéo pris en charge' },
89
- 'demo_walkthrough_start' => { title: 'Présentation des fournisseurs vidéo' },
90
- 'demo_youtube' => { title: 'Tutoriel YouTube' },
91
- 'demo_vimeo' => { title: 'Tutoriel Vimeo' },
92
- 'demo_loom' => { title: 'Tutoriel Loom' },
93
- 'demo_tella' => { title: 'Tutoriel Tella' },
94
- 'demo_voomly' => { title: 'Tutoriel Voomly' },
95
- 'demo_direct_video' => { title: 'Tutoriel vidéo MP4' },
96
- 'demo_getting_started' => { title: 'Bien démarrer', action_label: 'Ouvrir les paramètres' },
97
- 'demo_draft' => { title: 'Tutoriel brouillon' }
109
+ 'demo_walkthrough_finish' => {
110
+ title: 'Vous avez terminé la visite de Product Tours',
111
+ description: '<p>Cette étape émet <code>product_tours.completed</code>. Remplacez ces exemples par ' \
112
+ 'un guide court lié à un vrai moment de réussite client.</p>',
113
+ action_label: 'Terminer la visite'
114
+ },
115
+ 'demo_walkthrough_features' => {
116
+ title: 'Déclenchez une visite depuis votre interface',
117
+ description: '<p>Ajoutez <code>data-product-tour="demo_walkthrough_start"</code> à un bouton ou un lien. ' \
118
+ '<code>product_tours_tag</code> gère tous les déclencheurs de la page.</p>'
119
+ },
120
+ 'demo_walkthrough_start' => {
121
+ title: 'Bienvenue dans Product Tours',
122
+ description: '<p>Ceci est une vraie visite en plusieurs étapes créée par ' \
123
+ '<code>product_tours:seed_demo</code>. Continuez pour découvrir son API publique.</p>'
124
+ },
125
+ 'demo_youtube' => {
126
+ title: 'Ajoutez une vidéo seulement si elle aide',
127
+ description: '<p>Cette vidéo YouTube générique illustre seulement le fournisseur. Remplacez-la par la ' \
128
+ 'vôtre, ou laissez le champ vide pour un guide textuel rapide.</p>'
129
+ },
130
+ 'demo_vimeo' => {
131
+ title: 'Enchaînez des étapes ciblées',
132
+ description: '<p>Le bouton Suivant utilise <code>action_post_key</code>. Reliez des guides sans écrire ' \
133
+ 'de JavaScript dans votre application.</p>'
134
+ },
135
+ 'demo_loom' => {
136
+ title: 'Préparez en brouillon, puis publiez',
137
+ description: '<p>Le post <code>demo_draft</code> est visible dans le tableau de bord, mais pas pour les ' \
138
+ 'visiteurs. Vérifiez le contenu avant de publier.</p>'
139
+ },
140
+ 'demo_tella' => {
141
+ title: 'Traduisez une clé stable',
142
+ description: '<p>Une visite garde la même clé dans chaque langue. Le déclencheur ne doit donc jamais ' \
143
+ 'connaître la clé traduite.</p>'
144
+ },
145
+ 'demo_voomly' => {
146
+ title: 'Observez le cycle de vie',
147
+ description: '<p>Abonnez-vous aux événements viewed, dismissed, completed et unresolved-trigger, ' \
148
+ 'ou utilisez <code>config.on_event</code> avec la requête.</p>'
149
+ },
150
+ 'demo_direct_video' => {
151
+ title: 'Gardez le choix de la diffusion vidéo',
152
+ description: '<p>Cet exemple utilise un MP4 direct. Active Storage permet aussi les fichiers envoyés, ' \
153
+ 'diffusés par le moteur sans URL de blob publique.</p>'
154
+ },
155
+ 'demo_getting_started' => {
156
+ title: "Ouvrez l'action réelle suivante",
157
+ description: '<p>Une URL d’action termine la visite et envoie le visiteur vers la page où il peut ' \
158
+ 'appliquer ce qu’il vient d’apprendre.</p>',
159
+ action_label: "Essayer l'URL d'action"
160
+ },
161
+ 'demo_draft' => {
162
+ title: 'Ce brouillon reste invisible aux visiteurs',
163
+ description: '<p>Modifiez-le dans le tableau de bord : son déclencheur ne fonctionnera qu’après sa ' \
164
+ 'publication.</p>'
165
+ }
98
166
  },
99
167
  'bg' => {
100
- 'demo_walkthrough_finish' => { title: 'Готови сте', action_label: 'Започнете' },
101
- 'demo_walkthrough_features' => { title: 'Сравнете всички поддържани видео доставчици' },
102
- 'demo_walkthrough_start' => { title: 'Обиколка на видео доставчиците' },
103
- 'demo_youtube' => { title: 'Видео урок в YouTube' },
104
- 'demo_vimeo' => { title: 'Видео урок във Vimeo' },
105
- 'demo_loom' => { title: 'Видео урок в Loom' },
106
- 'demo_tella' => { title: 'Видео урок в Tella' },
107
- 'demo_voomly' => { title: 'Видео урок във Voomly' },
108
- 'demo_direct_video' => { title: 'MP4 видео урок' },
109
- 'demo_getting_started' => { title: 'Първи стъпки', action_label: 'Отвори настройките' },
110
- 'demo_draft' => { title: 'Чернова на урок' }
168
+ 'demo_walkthrough_finish' => {
169
+ title: 'Завършихте обиколката на Product Tours',
170
+ description: '<p>Тази стъпка излъчва <code>product_tours.completed</code>. Заменете примерите с кратък ' \
171
+ 'урок, свързан с реален успех на клиента.</p>',
172
+ action_label: 'Край на обиколката'
173
+ },
174
+ 'demo_walkthrough_features' => {
175
+ title: 'Стартирайте обиколка от вашия интерфейс',
176
+ description: '<p>Добавете <code>data-product-tour="demo_walkthrough_start"</code> към бутон или връзка. ' \
177
+ '<code>product_tours_tag</code> обслужва всички тригери на страницата.</p>'
178
+ },
179
+ 'demo_walkthrough_start' => {
180
+ title: 'Добре дошли в Product Tours',
181
+ description: '<p>Това е истинска обиколка от няколко стъпки, създадена от ' \
182
+ '<code>product_tours:seed_demo</code>. Продължете, за да научите публичния API.</p>'
183
+ },
184
+ 'demo_youtube' => {
185
+ title: 'Добавяйте видео само когато помага',
186
+ description: '<p>Този общ YouTube клип е само пример за доставчик. Заменете го със свой или оставете ' \
187
+ 'полето празно за бърз текстов урок.</p>'
188
+ },
189
+ 'demo_vimeo' => {
190
+ title: 'Свържете кратки, фокусирани стъпки',
191
+ description: '<p>Бутонът Напред използва <code>action_post_key</code>. Свързвайте уроци без JavaScript ' \
192
+ 'в приложението домакин.</p>'
193
+ },
194
+ 'demo_loom' => {
195
+ title: 'Подгответе чернова, после публикувайте',
196
+ description: '<p>Публикацията <code>demo_draft</code> се вижда в таблото, но не и от посетителите. ' \
197
+ 'Прегледайте съдържанието преди публикуване.</p>'
198
+ },
199
+ 'demo_tella' => {
200
+ title: 'Преведете един постоянен ключ',
201
+ description: '<p>Една обиколка използва същия ключ на всеки език. Тригерът никога не се нуждае от ' \
202
+ 'преведен ключ.</p>'
203
+ },
204
+ 'demo_voomly' => {
205
+ title: 'Наблюдавайте жизнения цикъл',
206
+ description: '<p>Абонирайте се за viewed, dismissed, completed и unresolved-trigger или използвайте ' \
207
+ '<code>config.on_event</code> с текущата заявка.</p>'
208
+ },
209
+ 'demo_direct_video' => {
210
+ title: 'Изберете как да доставяте видеото',
211
+ description: '<p>Този пример използва директен MP4. Active Storage поддържа и качени файлове, които ' \
212
+ 'се предават през двигателя без публичен blob URL.</p>'
213
+ },
214
+ 'demo_getting_started' => {
215
+ title: 'Отворете следващото реално действие',
216
+ description: '<p>URL действието завършва урока и отвежда посетителя до мястото, където може веднага ' \
217
+ 'да използва наученото.</p>',
218
+ action_label: 'Изпробвайте URL действието'
219
+ },
220
+ 'demo_draft' => {
221
+ title: 'Тази чернова остава скрита за посетители',
222
+ description: '<p>Редактирайте я в таблото; нейният тригер няма да работи, докато не я ' \
223
+ 'публикувате.</p>'
224
+ }
111
225
  }
112
226
  }.freeze
113
227
 
@@ -117,7 +231,10 @@ module ProductTours
117
231
  key = attributes.fetch(:key)
118
232
  localized = TRANSLATIONS.fetch(locale, {}).fetch(key, {})
119
233
  post = ProductTours::Post.find_or_initialize_by(locale: locale, key: key)
120
- post.assign_attributes(attributes.merge(localized).merge(status: attributes.fetch(:status, status)))
234
+ seed_attributes = attributes.merge(localized).merge(status: attributes.fetch(:status, status))
235
+ description = seed_attributes.delete(:description)
236
+ post.assign_attributes(seed_attributes)
237
+ post.description = description if ProductTours::Post.description_supported?
121
238
  post.save!
122
239
  post
123
240
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProductTours
4
- VERSION = '0.3.1'
4
+ VERSION = '0.3.3'
5
5
  end
@@ -90,6 +90,8 @@
90
90
 
91
91
  function renderPost(post, source) {
92
92
  if (!dialog) return;
93
+ var hasVideo = Boolean(post.video && post.video.url);
94
+ if (hasVideo) dialog.classList.add("pt-dialog-video");
93
95
  var existing = dialog.querySelector(".pt-content");
94
96
  if (existing) existing.remove();
95
97
  currentPost = post;
@@ -108,7 +110,7 @@
108
110
  head.appendChild(closeButton);
109
111
  content.appendChild(head);
110
112
 
111
- if (post.video && post.video.url) content.appendChild(videoNode(post.video, post.title));
113
+ if (hasVideo) content.appendChild(videoNode(post.video, post.title));
112
114
 
113
115
  if (post.descriptionHtml) {
114
116
  var description = element("div", "pt-description");
@@ -332,6 +334,7 @@
332
334
  ".pt-overlay{position:fixed;inset:0;z-index:2147482000;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;padding:16px}",
333
335
  ".pt-overlay,.pt-overlay *{box-sizing:border-box}",
334
336
  ".pt-dialog{width:100%;max-width:440px;max-height:92vh;overflow:auto;overscroll-behavior:contain;background:#fff;color:#1c2024;border-radius:14px;padding:20px;font:14px/1.5 system-ui,-apple-system,sans-serif;box-shadow:0 20px 60px rgba(0,0,0,.35);outline:none}",
337
+ ".pt-dialog-video{max-width:760px}",
335
338
  ".pt-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin:0 0 12px}",
336
339
  ".pt-title{margin:0;font-size:17px;line-height:1.5;font-weight:700;overflow-wrap:anywhere}",
337
340
  ".pt-close{border:0;background:none;font-size:22px;line-height:1;cursor:pointer;color:inherit;padding:2px 6px}",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: product_tours
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
131
  - !ruby/object:Gem::Version
132
132
  version: '0'
133
133
  requirements: []
134
- rubygems_version: 3.6.9
134
+ rubygems_version: 4.0.10
135
135
  specification_version: 4
136
136
  summary: Self-hosted product tours and video tutorials for Rails apps.
137
137
  test_files: []