product_tours 0.1.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +41 -0
  3. data/MIT-LICENSE +19 -0
  4. data/README.md +209 -0
  5. data/Rakefile +11 -0
  6. data/app/controllers/product_tours/application_controller.rb +68 -0
  7. data/app/controllers/product_tours/media_controller.rb +14 -0
  8. data/app/controllers/product_tours/posts_controller.rb +230 -0
  9. data/app/controllers/product_tours/tours_controller.rb +69 -0
  10. data/app/controllers/product_tours/widgets_controller.rb +28 -0
  11. data/app/helpers/product_tours/posts_helper.rb +19 -0
  12. data/app/helpers/product_tours/widget_helper.rb +14 -0
  13. data/app/models/product_tours/application_record.rb +7 -0
  14. data/app/models/product_tours/post.rb +128 -0
  15. data/app/views/layouts/product_tours/application.html.erb +20 -0
  16. data/app/views/product_tours/posts/_form.html.erb +177 -0
  17. data/app/views/product_tours/posts/_post_panel.html.erb +111 -0
  18. data/app/views/product_tours/posts/edit.html.erb +5 -0
  19. data/app/views/product_tours/posts/index.html.erb +92 -0
  20. data/app/views/product_tours/posts/new.html.erb +5 -0
  21. data/app/views/product_tours/posts/show.html.erb +5 -0
  22. data/config/locales/product_tours.ar.yml +11 -0
  23. data/config/locales/product_tours.bg.yml +11 -0
  24. data/config/locales/product_tours.bn.yml +11 -0
  25. data/config/locales/product_tours.de.yml +11 -0
  26. data/config/locales/product_tours.el.yml +11 -0
  27. data/config/locales/product_tours.en.yml +11 -0
  28. data/config/locales/product_tours.es.yml +11 -0
  29. data/config/locales/product_tours.fr.yml +11 -0
  30. data/config/locales/product_tours.hi.yml +11 -0
  31. data/config/locales/product_tours.hr.yml +11 -0
  32. data/config/locales/product_tours.id.yml +11 -0
  33. data/config/locales/product_tours.it.yml +11 -0
  34. data/config/locales/product_tours.ja.yml +11 -0
  35. data/config/locales/product_tours.ko.yml +11 -0
  36. data/config/locales/product_tours.lb.yml +11 -0
  37. data/config/locales/product_tours.nl.yml +11 -0
  38. data/config/locales/product_tours.pl.yml +11 -0
  39. data/config/locales/product_tours.pt.yml +11 -0
  40. data/config/locales/product_tours.ro.yml +11 -0
  41. data/config/locales/product_tours.ru.yml +11 -0
  42. data/config/locales/product_tours.th.yml +11 -0
  43. data/config/locales/product_tours.tr.yml +11 -0
  44. data/config/locales/product_tours.uk.yml +11 -0
  45. data/config/locales/product_tours.ur.yml +11 -0
  46. data/config/locales/product_tours.vi.yml +11 -0
  47. data/config/locales/product_tours.zh-CN.yml +11 -0
  48. data/config/routes.rb +23 -0
  49. data/lib/generators/product_tours/install/install_generator.rb +44 -0
  50. data/lib/generators/product_tours/install/templates/create_product_tours_posts.rb.tt +29 -0
  51. data/lib/generators/product_tours/install/templates/initializer.rb +21 -0
  52. data/lib/product_tours/configuration.rb +18 -0
  53. data/lib/product_tours/content_security_policy.rb +24 -0
  54. data/lib/product_tours/dashboard.css +196 -0
  55. data/lib/product_tours/dashboard.js +229 -0
  56. data/lib/product_tours/engine.rb +28 -0
  57. data/lib/product_tours/errors.rb +14 -0
  58. data/lib/product_tours/seeds.rb +147 -0
  59. data/lib/product_tours/version.rb +5 -0
  60. data/lib/product_tours/video_metadata.rb +60 -0
  61. data/lib/product_tours/video_resolver.rb +129 -0
  62. data/lib/product_tours/widget.js +359 -0
  63. data/lib/product_tours/widget.rb +59 -0
  64. data/lib/product_tours.rb +35 -0
  65. data/lib/tasks/product_tours_tasks.rake +12 -0
  66. metadata +131 -0
@@ -0,0 +1,11 @@
1
+ bg:
2
+ product_tours:
3
+ close: "Затвори"
4
+ back: "Назад"
5
+ next: "Напред"
6
+ done: "Готово"
7
+ dialog: "Урок за продукта"
8
+ video: "Видео урок"
9
+ unavailable: "Този урок не е наличен."
10
+ dashboard:
11
+ title: "Продуктови обиколки"
@@ -0,0 +1,11 @@
1
+ bn:
2
+ product_tours:
3
+ close: "বন্ধ করুন"
4
+ back: "ফিরে যান"
5
+ next: "পরবর্তী"
6
+ done: "সম্পন্ন"
7
+ dialog: "পণ্য টিউটোরিয়াল"
8
+ video: "টিউটোরিয়াল ভিডিও"
9
+ unavailable: "এই টিউটোরিয়ালটি উপলভ্য নয়।"
10
+ dashboard:
11
+ title: "পণ্য পরিচিতি"
@@ -0,0 +1,11 @@
1
+ de:
2
+ product_tours:
3
+ close: "Schließen"
4
+ back: "Zurück"
5
+ next: "Weiter"
6
+ done: "Fertig"
7
+ dialog: "Produkttutorial"
8
+ video: "Tutorialvideo"
9
+ unavailable: "Dieses Tutorial ist nicht verfügbar."
10
+ dashboard:
11
+ title: "Produkttouren"
@@ -0,0 +1,11 @@
1
+ el:
2
+ product_tours:
3
+ close: "Κλείσιμο"
4
+ back: "Πίσω"
5
+ next: "Επόμενο"
6
+ done: "Τέλος"
7
+ dialog: "Οδηγός προϊόντος"
8
+ video: "Βίντεο οδηγός"
9
+ unavailable: "Αυτός ο οδηγός δεν είναι διαθέσιμος."
10
+ dashboard:
11
+ title: "Περιηγήσεις προϊόντος"
@@ -0,0 +1,11 @@
1
+ en:
2
+ product_tours:
3
+ close: "Close"
4
+ back: "Back"
5
+ next: "Next"
6
+ done: "Done"
7
+ dialog: "Product tutorial"
8
+ video: "Tutorial video"
9
+ unavailable: "This tutorial is unavailable."
10
+ dashboard:
11
+ title: "Product tours"
@@ -0,0 +1,11 @@
1
+ es:
2
+ product_tours:
3
+ close: "Cerrar"
4
+ back: "Atrás"
5
+ next: "Siguiente"
6
+ done: "Listo"
7
+ dialog: "Tutorial del producto"
8
+ video: "Video tutorial"
9
+ unavailable: "Este tutorial no está disponible."
10
+ dashboard:
11
+ title: "Recorridos del producto"
@@ -0,0 +1,11 @@
1
+ fr:
2
+ product_tours:
3
+ close: "Fermer"
4
+ back: "Retour"
5
+ next: "Suivant"
6
+ done: "Terminé"
7
+ dialog: "Tutoriel produit"
8
+ video: "Vidéo du tutoriel"
9
+ unavailable: "Ce tutoriel n’est pas disponible."
10
+ dashboard:
11
+ title: "Visites guidées"
@@ -0,0 +1,11 @@
1
+ hi:
2
+ product_tours:
3
+ close: "बंद करें"
4
+ back: "वापस"
5
+ next: "अगला"
6
+ done: "पूरा हुआ"
7
+ dialog: "उत्पाद ट्यूटोरियल"
8
+ video: "ट्यूटोरियल वीडियो"
9
+ unavailable: "यह ट्यूटोरियल उपलब्ध नहीं है।"
10
+ dashboard:
11
+ title: "उत्पाद परिचय"
@@ -0,0 +1,11 @@
1
+ hr:
2
+ product_tours:
3
+ close: "Zatvori"
4
+ back: "Natrag"
5
+ next: "Dalje"
6
+ done: "Gotovo"
7
+ dialog: "Vodič kroz proizvod"
8
+ video: "Video vodič"
9
+ unavailable: "Ovaj vodič nije dostupan."
10
+ dashboard:
11
+ title: "Vodiči kroz proizvod"
@@ -0,0 +1,11 @@
1
+ id:
2
+ product_tours:
3
+ close: "Tutup"
4
+ back: "Kembali"
5
+ next: "Berikutnya"
6
+ done: "Selesai"
7
+ dialog: "Tutorial produk"
8
+ video: "Video tutorial"
9
+ unavailable: "Tutorial ini tidak tersedia."
10
+ dashboard:
11
+ title: "Tur produk"
@@ -0,0 +1,11 @@
1
+ it:
2
+ product_tours:
3
+ close: "Chiudi"
4
+ back: "Indietro"
5
+ next: "Avanti"
6
+ done: "Fatto"
7
+ dialog: "Tutorial del prodotto"
8
+ video: "Video tutorial"
9
+ unavailable: "Questo tutorial non è disponibile."
10
+ dashboard:
11
+ title: "Tour del prodotto"
@@ -0,0 +1,11 @@
1
+ ja:
2
+ product_tours:
3
+ close: "閉じる"
4
+ back: "戻る"
5
+ next: "次へ"
6
+ done: "完了"
7
+ dialog: "製品チュートリアル"
8
+ video: "チュートリアル動画"
9
+ unavailable: "このチュートリアルは利用できません。"
10
+ dashboard:
11
+ title: "製品ツアー"
@@ -0,0 +1,11 @@
1
+ ko:
2
+ product_tours:
3
+ close: "닫기"
4
+ back: "뒤로"
5
+ next: "다음"
6
+ done: "완료"
7
+ dialog: "제품 튜토리얼"
8
+ video: "튜토리얼 동영상"
9
+ unavailable: "이 튜토리얼을 사용할 수 없습니다."
10
+ dashboard:
11
+ title: "제품 둘러보기"
@@ -0,0 +1,11 @@
1
+ lb:
2
+ product_tours:
3
+ close: "Zoumaachen"
4
+ back: "Zeréck"
5
+ next: "Weider"
6
+ done: "Fäerdeg"
7
+ dialog: "Produktutorial"
8
+ video: "Tutorialvideo"
9
+ unavailable: "Dësen Tutorial ass net verfügbar."
10
+ dashboard:
11
+ title: "Produkttouren"
@@ -0,0 +1,11 @@
1
+ nl:
2
+ product_tours:
3
+ close: "Sluiten"
4
+ back: "Terug"
5
+ next: "Volgende"
6
+ done: "Klaar"
7
+ dialog: "Producttutorial"
8
+ video: "Tutorialvideo"
9
+ unavailable: "Deze tutorial is niet beschikbaar."
10
+ dashboard:
11
+ title: "Productrondleidingen"
@@ -0,0 +1,11 @@
1
+ pl:
2
+ product_tours:
3
+ close: "Zamknij"
4
+ back: "Wstecz"
5
+ next: "Dalej"
6
+ done: "Gotowe"
7
+ dialog: "Samouczek produktu"
8
+ video: "Film instruktażowy"
9
+ unavailable: "Ten samouczek jest niedostępny."
10
+ dashboard:
11
+ title: "Prezentacje produktu"
@@ -0,0 +1,11 @@
1
+ pt:
2
+ product_tours:
3
+ close: "Fechar"
4
+ back: "Voltar"
5
+ next: "Seguinte"
6
+ done: "Concluir"
7
+ dialog: "Tutorial do produto"
8
+ video: "Vídeo tutorial"
9
+ unavailable: "Este tutorial não está disponível."
10
+ dashboard:
11
+ title: "Tours do produto"
@@ -0,0 +1,11 @@
1
+ ro:
2
+ product_tours:
3
+ close: "Închide"
4
+ back: "Înapoi"
5
+ next: "Înainte"
6
+ done: "Gata"
7
+ dialog: "Tutorial de produs"
8
+ video: "Videoclip tutorial"
9
+ unavailable: "Acest tutorial nu este disponibil."
10
+ dashboard:
11
+ title: "Tururi de produs"
@@ -0,0 +1,11 @@
1
+ ru:
2
+ product_tours:
3
+ close: "Закрыть"
4
+ back: "Назад"
5
+ next: "Далее"
6
+ done: "Готово"
7
+ dialog: "Руководство по продукту"
8
+ video: "Обучающее видео"
9
+ unavailable: "Это руководство недоступно."
10
+ dashboard:
11
+ title: "Обзоры продукта"
@@ -0,0 +1,11 @@
1
+ th:
2
+ product_tours:
3
+ close: "ปิด"
4
+ back: "ย้อนกลับ"
5
+ next: "ถัดไป"
6
+ done: "เสร็จสิ้น"
7
+ dialog: "บทแนะนำผลิตภัณฑ์"
8
+ video: "วิดีโอบทแนะนำ"
9
+ unavailable: "บทแนะนำนี้ไม่พร้อมใช้งาน"
10
+ dashboard:
11
+ title: "ทัวร์ผลิตภัณฑ์"
@@ -0,0 +1,11 @@
1
+ tr:
2
+ product_tours:
3
+ close: "Kapat"
4
+ back: "Geri"
5
+ next: "İleri"
6
+ done: "Bitti"
7
+ dialog: "Ürün eğitimi"
8
+ video: "Eğitim videosu"
9
+ unavailable: "Bu eğitim kullanılamıyor."
10
+ dashboard:
11
+ title: "Ürün turları"
@@ -0,0 +1,11 @@
1
+ uk:
2
+ product_tours:
3
+ close: "Закрити"
4
+ back: "Назад"
5
+ next: "Далі"
6
+ done: "Готово"
7
+ dialog: "Посібник із продукту"
8
+ video: "Навчальне відео"
9
+ unavailable: "Цей посібник недоступний."
10
+ dashboard:
11
+ title: "Огляди продукту"
@@ -0,0 +1,11 @@
1
+ ur:
2
+ product_tours:
3
+ close: "بند کریں"
4
+ back: "واپس"
5
+ next: "اگلا"
6
+ done: "مکمل"
7
+ dialog: "پروڈکٹ ٹیوٹوریل"
8
+ video: "ٹیوٹوریل ویڈیو"
9
+ unavailable: "یہ ٹیوٹوریل دستیاب نہیں ہے۔"
10
+ dashboard:
11
+ title: "پروڈکٹ ٹورز"
@@ -0,0 +1,11 @@
1
+ vi:
2
+ product_tours:
3
+ close: "Đóng"
4
+ back: "Quay lại"
5
+ next: "Tiếp theo"
6
+ done: "Xong"
7
+ dialog: "Hướng dẫn sản phẩm"
8
+ video: "Video hướng dẫn"
9
+ unavailable: "Hướng dẫn này không khả dụng."
10
+ dashboard:
11
+ title: "Chuyến tham quan sản phẩm"
@@ -0,0 +1,11 @@
1
+ "zh-CN":
2
+ product_tours:
3
+ close: "关闭"
4
+ back: "返回"
5
+ next: "下一步"
6
+ done: "完成"
7
+ dialog: "产品教程"
8
+ video: "教程视频"
9
+ unavailable: "此教程不可用。"
10
+ dashboard:
11
+ title: "产品导览"
data/config/routes.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ ProductTours::Engine.routes.draw do
4
+ get 'widget.js', to: 'widgets#show', as: :widget_script
5
+ get 'dashboard.js', to: 'widgets#dashboard', as: :dashboard_script
6
+ get 'dashboard.css', to: 'widgets#dashboard_stylesheet', as: :dashboard_stylesheet
7
+
8
+ scope :widget, as: :widget do
9
+ get :post, to: 'tours#resolve'
10
+ post :signal, to: 'tours#signal'
11
+ end
12
+
13
+ get 'media/:id', to: 'media#show', as: :media, constraints: { id: /\d+/ }
14
+
15
+ resources :posts do
16
+ post :refresh_video_metadata, on: :member
17
+ post :add_translation, on: :member
18
+ patch :publish, on: :member
19
+ patch :unpublish, on: :member
20
+ get :video_preview, on: :collection
21
+ end
22
+ root to: 'posts#index'
23
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module ProductTours
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path('templates', __dir__)
12
+ desc 'Installs product_tours: initializer, posts migration, and engine mount.'
13
+
14
+ def create_initializer
15
+ copy_file 'initializer.rb', 'config/initializers/product_tours.rb'
16
+ end
17
+
18
+ def create_migration_file
19
+ migration_template 'create_product_tours_posts.rb.tt', 'db/migrate/create_product_tours_posts.rb'
20
+ end
21
+
22
+ def mount_engine
23
+ route %(mount_product_tours at: "/product_tours")
24
+ end
25
+
26
+ def post_install
27
+ say "\nproduct_tours installed. Run `bin/rails db:migrate`.", :green
28
+ say 'The migration adds ready-to-use demo posts in development.'
29
+ say "\nCopy this into any ERB view to try them:\n\n"
30
+ say ProductTours::Seeds.trigger_html
31
+ say 'Manage tutorials at /product_tours (development only until config.authorize_admin is set).'
32
+ say 'Run `bin/rails product_tours:seed_demo` any time to refresh every demo locale.'
33
+ say 'Run `bin/rails active_storage:install` for uploads and '
34
+ say '`bin/rails action_text:install` for rich descriptions.\n'
35
+ end
36
+
37
+ private
38
+
39
+ def migration_version
40
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateProductToursPosts < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :product_tours_posts do |t|
6
+ t.string :key, null: false
7
+ t.string :locale, null: false
8
+ t.string :status, null: false, default: "draft"
9
+ t.string :title, null: false
10
+ t.text :video_url
11
+ t.json :video_metadata, null: false, default: {}
12
+ t.string :action_label
13
+ t.text :action_url
14
+ t.string :action_post_key
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ add_index :product_tours_posts, %i[locale key], unique: true
20
+ add_index :product_tours_posts, :locale
21
+ add_index :product_tours_posts, :status
22
+
23
+ reversible do |direction|
24
+ direction.up do
25
+ ProductTours::Seeds.load_for_install!
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ ProductTours.configure do |config|
4
+ # Who can see and invoke product tours. Defaults to everyone.
5
+ # config.enabled = ->(request) { true }
6
+
7
+ # Who can manage tutorials at the mount path. Defaults to development only.
8
+ # config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
9
+
10
+ # Render the dashboard in your own admin shell.
11
+ # config.admin_layout = "admin/application"
12
+
13
+ # Tutorials resolve in the page's current I18n.locale and fall back to
14
+ # I18n.default_locale when no translation exists.
15
+
16
+ # Optional dedicated Active Storage service for uploaded videos.
17
+ # config.storage_service = :product_tours
18
+
19
+ # Keep this in sync only when mounting the engine manually.
20
+ # config.mount_path = "/product_tours"
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProductTours
4
+ class Configuration
5
+ attr_accessor :enabled, :authorize_admin, :admin_layout, :mount_path,
6
+ :storage_service
7
+
8
+ def initialize
9
+ @enabled = ->(_request) { true }
10
+ @authorize_admin = ->(_request) { Rails.env.development? }
11
+ @admin_layout = 'product_tours/application'
12
+ @mount_path = '/product_tours'
13
+ @storage_service = nil
14
+ end
15
+
16
+ def widget_endpoint = "#{mount_path.to_s.chomp('/')}/widget"
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProductTours
4
+ module ContentSecurityPolicy
5
+ FRAME_SOURCES = %w[
6
+ https://www.youtube-nocookie.com
7
+ https://player.vimeo.com
8
+ https://www.loom.com
9
+ https://www.tella.tv
10
+ https://embed.voomly.com
11
+ ].freeze
12
+
13
+ module_function
14
+
15
+ def apply!(policy)
16
+ return unless policy
17
+
18
+ sources = Array(policy.directives['frame-src'])
19
+ sources = Array(policy.directives['default-src']) if sources.empty?
20
+ sources = sources.reject { |source| source == "'none'" }
21
+ policy.frame_src(*(sources + FRAME_SOURCES).uniq)
22
+ end
23
+ end
24
+ end