tybo 0.6.1 → 0.7.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/tybo_manifest.js +1 -0
  3. data/app/assets/images/cover.svg +36 -0
  4. data/app/assets/images/logo.svg +10 -0
  5. data/app/assets/javascripts/tybo/controllers/attachments_controller.js +13 -0
  6. data/app/assets/javascripts/tybo/controllers/dropdown_controller.js +15 -0
  7. data/app/assets/javascripts/tybo/controllers/flash_controller.js +7 -0
  8. data/app/assets/javascripts/tybo/controllers/index.js +7 -0
  9. data/app/assets/javascripts/tybo/controllers/questions_controller.js +62 -0
  10. data/app/assets/javascripts/tybo/controllers/search_form_controller.js +34 -0
  11. data/app/assets/javascripts/tybo/controllers/sidebar_controller.js +16 -0
  12. data/app/assets/javascripts/tybo/controllers/ts/search_controller.js +29 -0
  13. data/app/assets/javascripts/tybo/controllers/ts/select_controller.js +8 -0
  14. data/app/components/attachment_card_component.html.erb +1 -1
  15. data/app/components/forms/search_date_input_component.html.erb +2 -2
  16. data/app/components/sidebar_component.html.erb +5 -5
  17. data/app/views/layouts/_errors.html.erb +2 -2
  18. data/app/views/layouts/_flash.html.erb +2 -2
  19. data/app/views/layouts/devise_admin.html.erb +3 -2
  20. data/config/importmap.rb +0 -0
  21. data/lib/generators/bo/templates/_form.html.erb +1 -1
  22. data/lib/generators/bo/templates/_search_bar.html.erb +11 -11
  23. data/lib/generators/bo_namespace/bo_namespace_generator.rb +1 -1
  24. data/lib/generators/bo_namespace/templates/admin.html.erb +2 -4
  25. data/lib/generators/tybo_install/templates/application_tybo_admin.js +20 -0
  26. data/lib/generators/tybo_install/templates/tybo_admin.tailwind.css +112 -0
  27. data/lib/generators/tybo_install/templates/tybo_config.rb +1 -1
  28. data/lib/generators/tybo_install/tybo_install_generator.rb +29 -9
  29. data/lib/generators/tybo_install/utils/translations.rb +35 -2
  30. data/lib/generators/tybo_upgrade/tybo_upgrade_generator.rb +193 -0
  31. data/lib/puma/plugin/tybo.rb +36 -0
  32. data/lib/tasks/tybo.rake +46 -0
  33. data/lib/tybo/engine.rb +4 -0
  34. data/lib/tybo/version.rb +1 -1
  35. metadata +18 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b03e339acacab25bc28e360312e4da24a4b0de2dadca108808eb734b4a86451
4
- data.tar.gz: 1005be27243034961a0bfe8567ba5589898b0ce0ad8b54a7e4adeb997b7dc913
3
+ metadata.gz: ebad2a70d38a84816f82190a77c32ff5a2cd10477757176304fc6e1e399c8713
4
+ data.tar.gz: f6a70f5f02ef98faf6fa371a4771151741ffbd6a3134e9dc4a56d3ac754f9251
5
5
  SHA512:
6
- metadata.gz: 819e313984b9e138d4527aafc04d17f21cf23192d91b8109bf48a4b5aae39c5c2da6f519193fc3ab87bece2a050d543f307fbd42839710d90259a301fd9b52a6
7
- data.tar.gz: 07ce9d3f28abcb492c9dcc88ad44451a47df69e96c2f70953769ffe2d636dfa21401bf8758bc6e7171e48d5bfb44dd5867250f6beeb0c3c433dc85be98b152fb
6
+ metadata.gz: a931cec4abad743ce1996da98fe63332f9cf30eba3c647d16cb931abecedc452355a0fdf48e64a5d2a4db7aaf5d46961bd7199a1527e774bb697af8acc1d5595
7
+ data.tar.gz: 8a20e59bfdf5db506a27494ed540a81ef5ef51d9b50b20c7cd50b65f8799e0fbd2bb2e226496679f5bb88cdcfbd04d45d690caefe10ede25fa4ec17cf38809a9
@@ -1 +1,2 @@
1
1
  //= link_tree ../builds/ .css
2
+ //= link_tree ../javascripts/tybo .js
@@ -0,0 +1,36 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" fill="none">
2
+ <!-- Background gradient -->
3
+ <defs>
4
+ <linearGradient id="bg" x1="0" y1="0" x2="800" y2="600" gradientUnits="userSpaceOnUse">
5
+ <stop offset="0%" stop-color="#3b0764"/>
6
+ <stop offset="100%" stop-color="#581c87"/>
7
+ </linearGradient>
8
+ <linearGradient id="glow" x1="0" y1="0" x2="1" y2="1">
9
+ <stop offset="0%" stop-color="#c084fc" stop-opacity="0.3"/>
10
+ <stop offset="100%" stop-color="#581c87" stop-opacity="0"/>
11
+ </linearGradient>
12
+ </defs>
13
+
14
+ <rect width="800" height="600" fill="url(#bg)"/>
15
+
16
+ <!-- Decorative circles -->
17
+ <circle cx="650" cy="100" r="200" fill="url(#glow)"/>
18
+ <circle cx="150" cy="500" r="150" fill="url(#glow)"/>
19
+
20
+ <!-- Grid lines subtle -->
21
+ <line x1="0" y1="150" x2="800" y2="150" stroke="#c084fc" stroke-opacity="0.08" stroke-width="1"/>
22
+ <line x1="0" y1="300" x2="800" y2="300" stroke="#c084fc" stroke-opacity="0.08" stroke-width="1"/>
23
+ <line x1="0" y1="450" x2="800" y2="450" stroke="#c084fc" stroke-opacity="0.08" stroke-width="1"/>
24
+ <line x1="200" y1="0" x2="200" y2="600" stroke="#c084fc" stroke-opacity="0.08" stroke-width="1"/>
25
+ <line x1="400" y1="0" x2="400" y2="600" stroke="#c084fc" stroke-opacity="0.08" stroke-width="1"/>
26
+ <line x1="600" y1="0" x2="600" y2="600" stroke="#c084fc" stroke-opacity="0.08" stroke-width="1"/>
27
+
28
+ <!-- Logo text -->
29
+ <text x="400" y="290" font-family="ui-sans-serif, system-ui, sans-serif" font-size="96" font-weight="700" fill="white" text-anchor="middle" letter-spacing="-3" opacity="0.95">tybo</text>
30
+
31
+ <!-- Tagline -->
32
+ <text x="400" y="340" font-family="ui-sans-serif, system-ui, sans-serif" font-size="18" fill="#c084fc" text-anchor="middle" letter-spacing="4" opacity="0.8">ADMIN FRAMEWORK</text>
33
+
34
+ <!-- Accent dot -->
35
+ <circle cx="530" cy="228" r="8" fill="#c084fc" opacity="0.9"/>
36
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 48" fill="none">
2
+ <!-- Background pill -->
3
+ <rect width="160" height="48" rx="12" fill="#581c87"/>
4
+
5
+ <!-- T -->
6
+ <text x="16" y="34" font-family="ui-sans-serif, system-ui, sans-serif" font-size="28" font-weight="700" fill="white" letter-spacing="-1">tybo</text>
7
+
8
+ <!-- Accent dot -->
9
+ <circle cx="144" cy="12" r="5" fill="#c084fc"/>
10
+ </svg>
@@ -0,0 +1,13 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { FetchRequest } from '@rails/request.js'
3
+
4
+ export default class extends Controller {
5
+ static values = { url: String, method: String, attachmentId: Number }
6
+
7
+ async toggle(event) {
8
+ event.preventDefault()
9
+
10
+ const request = new FetchRequest(this.methodValue, this.urlValue, { responseKind: "turbo-stream", body: { attachment_id: this.attachmentIdValue } })
11
+ await request.perform()
12
+ }
13
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["menu"]
5
+
6
+ toggle() {
7
+ this.menuTarget.classList.toggle("hidden")
8
+ }
9
+
10
+ hide(event) {
11
+ if (!this.element.contains(event.target) && !this.menuTarget.classList.contains('hidden')) {
12
+ this.menuTarget.classList.add("hidden")
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ dismiss() {
5
+ this.element.remove()
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ export { default as Attachments } from "./attachments_controller.js";
2
+ export { default as Dropdown } from "./dropdown_controller.js";
3
+ export { default as Flash } from "./flash_controller.js";
4
+ export { default as SearchForm } from "./search_form_controller.js";
5
+ export { default as TsSearch } from "./ts/search_controller.js";
6
+ export { default as TsSelect } from "./ts/select_controller.js";
7
+ export { default as Sidebar } from "./sidebar_controller.js";
@@ -0,0 +1,62 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ['questionKind', 'questionType', 'newQuestion', 'choices', 'documentType']
5
+
6
+ changeType(e) {
7
+ let questionType = e.target.selectedOptions[0].value
8
+ let kindInput = this.questionKindTarget
9
+ this.getKindInput(questionType, kindInput)
10
+ }
11
+
12
+ getKindInput(questionType, kindInput) {
13
+ fetch(`/admin/questions/kinds?type=${questionType}`, {
14
+ method: 'GET',
15
+ }).then(response => response.json())
16
+ .then(data => {
17
+ kindInput.options.length = 0
18
+ data.unshift('')
19
+ for (let i in data) {
20
+ kindInput.options[kindInput.options.length] = new Option(data[i][0], data[i][1]);
21
+ }
22
+ this.displayChoiceBtn()
23
+ })
24
+ }
25
+
26
+ changeKind() {
27
+ this.displayChoiceBtn()
28
+ this.removeChoices()
29
+ }
30
+
31
+ removeChoices() {
32
+ document.getElementById('choices').innerHTML = ''
33
+ }
34
+
35
+ updateChoicePartial() {
36
+ let questionKind = this.questionKindTarget.selectedOptions[0].value
37
+ let questionType = this.questionTypeTarget.selectedOptions[0].value
38
+ let choices = this.choicesTarget
39
+ fetch(`/admin/questions/update_nested_form?type=${questionType}&kind=${questionKind}`, {
40
+ method: 'GET',
41
+ }).then(response => response.json())
42
+ .then(data => {
43
+ choices.innerHTML = data
44
+ })
45
+ }
46
+
47
+ displayChoiceBtn() {
48
+ let questionKind = this.questionKindTarget.selectedOptions[0].value
49
+ let questionType = this.questionTypeTarget.selectedOptions[0].value
50
+ let btn = document.getElementById('add-choice')
51
+ if (Boolean(questionKind) && Boolean(questionType) && questionType != "Questions::Input") {
52
+ btn.classList.remove('hidden')
53
+ } else {
54
+ btn.classList.add('hidden')
55
+ }
56
+ }
57
+
58
+ addQuestion() {
59
+ this.newQuestionTargets[0].classList.remove('hidden')
60
+ location.hash = "#" + 'newQuestion';
61
+ }
62
+ }
@@ -0,0 +1,34 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["form"]
5
+
6
+ search() {
7
+ const exportBtn = document.getElementById("exportBtn")
8
+ let originalHref = exportBtn.getAttribute("href")
9
+ if (exportBtn) {
10
+ originalHref = originalHref.split('?')[0]
11
+
12
+ const params = this.getQueryParams()
13
+ exportBtn.setAttribute("href", `${originalHref}?${params}`)
14
+ }
15
+ clearTimeout(this.timeout)
16
+ this.timeout = setTimeout(() => {
17
+ this.formTarget.requestSubmit()
18
+ }, 200)
19
+ }
20
+
21
+ getQueryParams() {
22
+ const formData = new FormData(this.formTarget)
23
+ const params = new URLSearchParams(formData).toString()
24
+ return params
25
+ }
26
+
27
+ setBooleanField(e) {
28
+ let value = e.target.getAttribute('data-value')
29
+ let targetId = e.target.getAttribute('data-target-id')
30
+ let target = document.getElementById(targetId)
31
+ target.value = value
32
+ this.formTarget.requestSubmit()
33
+ }
34
+ }
@@ -0,0 +1,16 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["sidebar", "bg"]
5
+
6
+ toggle() {
7
+ this.sidebarTarget.classList.toggle("-translate-x-full")
8
+ this.bgTarget.classList.toggle("opacity-100")
9
+ }
10
+
11
+ close() {
12
+ this.sidebarTarget.classList.add("-translate-x-full")
13
+ this.bgTarget.classList.remove("opacity-100")
14
+ this.bgTarget.classList.add("opacity-0")
15
+ }
16
+ }
@@ -0,0 +1,29 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { get } from '@rails/request.js'
3
+ import TomSelect from "tom-select"
4
+
5
+ export default class extends Controller {
6
+ static values = { url: String }
7
+
8
+ connect() {
9
+ new TomSelect(this.element, {
10
+ plugins: ['clear_button'],
11
+ valueField: 'value',
12
+ load: (q, callback) => this.search(q, callback)
13
+ })
14
+ }
15
+
16
+ async search(q, callback) {
17
+ const response = await get(this.urlValue, {
18
+ query: { q: q },
19
+ responseKind: 'json'
20
+ })
21
+
22
+ if (response.ok) {
23
+ const list = await response.json
24
+ callback(list)
25
+ } else {
26
+ callback()
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,8 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import TomSelect from "tom-select"
3
+
4
+ export default class extends Controller {
5
+ connect() {
6
+ new TomSelect(this.element, { create: false, plugins: ['remove_button'] })
7
+ }
8
+ }
@@ -17,7 +17,7 @@
17
17
  </div>
18
18
 
19
19
  <div class="flex-shrink-0 pr-2">
20
- <%= button_tag type: :button, data: { controller: "attachments", attachments_method_value: 'delete', attachments_url_value: @url, attachments_attachment_id_value: @attachment.id, action: "click->attachments#toggle" } do %>
20
+ <%= button_tag type: :button, data: { controller: "tybo--attachments", tybo__attachments_method_value: 'delete', tybo__attachments_url_value: @url, tybo__attachments_attachment_id_value: @attachment.id, action: "click->tybo--attachments#toggle" } do %>
21
21
  <span class="sr-only">Delete</span>
22
22
  <%= render(Icons::TrashComponent.new) %>
23
23
  <% end %>
@@ -5,12 +5,12 @@
5
5
  </label>
6
6
  </div>
7
7
  <div class="mt-1 sm:col-span-3 sm:mt-0">
8
- <%= @form.date_field @from_field, data: { action: "input->search-form#search" }, class: "rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:text-sm" %>
8
+ <%= @form.date_field @from_field, data: { action: "input->tybo--search-form#search" }, class: "rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:text-sm" %>
9
9
  </div>
10
10
  <div class="mt-1 sm:col-span-2 text-center">
11
11
  <%= I18n.t('bo.to') %>
12
12
  </div>
13
13
  <div class="mt-1 sm:col-span-3 sm:mt-0">
14
- <%= @form.date_field @to_field, data: { action: "input->search-form#search" }, class: "rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:text-sm" %>
14
+ <%= @form.date_field @to_field, data: { action: "input->tybo--search-form#search" }, class: "rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:text-sm" %>
15
15
  </div>
16
16
  </div>
@@ -1,7 +1,7 @@
1
- <div data-controller="sidebar">
1
+ <div data-controller="tybo--sidebar">
2
2
  <!-- Barre de navigation mobile avec bouton d'ouverture du menu -->
3
3
  <div class="sticky top-0 z-40 flex items-center h-16 px-4 border-b border-gray-200 shadow-sm bg-tybo lg:hidden shrink-0 gap-x-4 sm:gap-x-6 sm:px-6 lg:px-8">
4
- <button type="button" class="-m-2.5 p-2.5 text-white lg:hidden" data-action="click->sidebar#toggle">
4
+ <button type="button" class="-m-2.5 p-2.5 text-white lg:hidden" data-action="click->tybo--sidebar#toggle">
5
5
  <span class="sr-only">Open sidebar</span>
6
6
  <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
7
7
  <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
@@ -9,12 +9,12 @@
9
9
  </button>
10
10
  </div>
11
11
  <!-- Sidebar mobile avec bouton de fermeture -->
12
- <div class="fixed inset-0 z-50 transition-transform duration-300 ease-in-out transform -translate-x-full" data-sidebar-target="sidebar">
13
- <div class="fixed inset-0 bg-gray-900/80 " aria-hidden="true" data-sidebar-target="bg"></div>
12
+ <div class="fixed inset-0 z-50 transition-transform duration-300 ease-in-out transform -translate-x-full" data-tybo--sidebar-target="sidebar">
13
+ <div class="fixed inset-0 bg-gray-900/80 " aria-hidden="true" data-tybo--sidebar-target="bg"></div>
14
14
  <div class="fixed inset-0 flex">
15
15
  <div class="relative flex flex-1 w-full max-w-xs mr-16">
16
16
  <div class="absolute top-0 flex justify-center w-16 pt-5 left-full">
17
- <button type="button" class="-m-2.5 p-2.5" data-action="click->sidebar#close">
17
+ <button type="button" class="-m-2.5 p-2.5" data-action="click->tybo--sidebar#close">
18
18
  <span class="sr-only">Close sidebar</span>
19
19
  <svg class="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" aria-hidden="true">
20
20
  <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
@@ -1,4 +1,4 @@
1
- <div class="rounded-md bg-red-alert text-white p-4 my-3" data-controller="flash">
1
+ <div class="rounded-md bg-red-alert text-white p-4 my-3" data-controller="tybo--flash">
2
2
  <div class="flex">
3
3
  <div class="flex-shrink-0">
4
4
  <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
  <div class="ml-auto pl-3">
12
12
  <div class="-mx-1.5 -my-1.5">
13
- <button type="button" data-action="flash#dismiss" class="inline-flex bg-red-50 rounded-md p-1.5 text-red-alert hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-red-50 focus:ring-red-600">
13
+ <button type="button" data-action="tybo--flash#dismiss" class="inline-flex bg-red-50 rounded-md p-1.5 text-red-alert hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-red-50 focus:ring-red-600">
14
14
  <span class="sr-only">Dismiss</span>
15
15
  <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
16
16
  <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"/>
@@ -1,5 +1,5 @@
1
1
  <% flash.each do |key, errors| %>
2
- <div class="rounded-md <%= classes_for_flash(key) %> p-4 m-4" data-controller="flash">
2
+ <div class="rounded-md <%= classes_for_flash(key) %> p-4 m-4" data-controller="tybo--flash">
3
3
  <div class="flex">
4
4
  <div class="flex-shrink-0">
5
5
  <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -11,7 +11,7 @@
11
11
  </div>
12
12
  <div class="ml-auto pl-3">
13
13
  <div class="-mx-1.5 -my-1.5">
14
- <button type="button" data-action="flash#dismiss" class="inline-flex rounded-md p-1.5 focus:outline-none focus:ring-2 focus:ring-offset-2">
14
+ <button type="button" data-action="tybo--flash#dismiss" class="inline-flex rounded-md p-1.5 focus:outline-none focus:ring-2 focus:ring-offset-2">
15
15
  <span class="sr-only"><%= I18n.t('bo.flash.close')%></span>
16
16
  <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
17
17
  <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"/>
@@ -1,11 +1,12 @@
1
1
  <!DOCTYPE html>
2
- <html class="h-full bg-white">
2
+ <html class="h-full bg-white">
3
3
  <head>
4
4
  <title>TY-BO</title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
8
- <%= javascript_importmap_tags %>
8
+ <%= stylesheet_link_tag "tybo_admin", "data-turbo-track": "reload" %>
9
+ <%= javascript_importmap_tags "application_tybo_admin" %>
9
10
  </head>
10
11
  <body class="h-full">
11
12
  <main class="flex min-h-full">
File without changes
@@ -44,7 +44,7 @@
44
44
  <%= association.klass.name %>.all.map { |item| [item.<%=bo_model_title(association.klass.name.constantize)%>, item.id] },
45
45
  { include_blank: true },
46
46
  multiple: true,
47
- data: { controller: 'ts--select' } %>
47
+ data: { controller: 'tybo--ts--select' } %>
48
48
  <%% end %>
49
49
  <%- end -%>
50
50
  <%%= render(Forms::SubmitButtonComponent.new) %>
@@ -1,14 +1,14 @@
1
- <div class="relative inline-block mt-3 text-left" data-controller="dropdown">
1
+ <div class="relative inline-block mt-3 text-left" data-controller="tybo--dropdown">
2
2
  <div>
3
- <button data-action="click->dropdown#toggle click@window->dropdown#hide" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none " id="menu-button" aria-expanded="true" aria-haspopup="true">
3
+ <button data-action="click->tybo--dropdown#toggle click@window->tybo--dropdown#hide" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none " id="menu-button" aria-expanded="true" aria-haspopup="true">
4
4
  <%%= I18n.t('bo.filters') %>
5
5
  <svg class="w-5 h-5 ml-2 -mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
6
6
  <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
7
7
  </svg>
8
8
  </button>
9
9
  </div>
10
- <div data-dropdown-target="menu" class="drop-shadow-md hidden absolute py-5 px-5 left-0 z-10 mt-2 w-[40rem] origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
11
- <%%= search_form_for [:<%=options[:namespace].underscore %>, @q], method: :post, class: "", data: { controller: "search-form", search_form_target: "form", turbo_frame: '<%=class_name.underscore.pluralize%>' } do |f| %>
10
+ <div data-tybo--dropdown-target="menu" class="drop-shadow-md hidden absolute py-5 px-5 left-0 z-10 mt-2 w-[40rem] origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
11
+ <%%= search_form_for [:<%=options[:namespace].underscore %>, @q], method: :post, class: "", data: { controller: "tybo--search-form", "tybo--search-form-target": "form", turbo_frame: '<%=class_name.underscore.pluralize%>' } do |f| %>
12
12
  <!-- columns -->
13
13
  <%- bo_model.columns.group_by(&:type).each do |type| -%>
14
14
  <%- type.second.sort_by(&:type).each do |col| -%>
@@ -16,11 +16,11 @@
16
16
  <%- next if bo_model.reflect_on_all_associations.map(&:foreign_key).include?(col.name) -%>
17
17
  <%- if col.type == :string || col.type == :text -%>
18
18
  <%%= render(Forms::SearchInputComponent.new(label: I18n.t('bo.<%= class_name.underscore %>.attributes.<%= col.name %>'))) do %>
19
- <%%= f.text_field :<%= col.name %>_cont, placeholder: I18n.t('bo.<%= class_name.underscore %>.attributes.<%= col.name %>'), data: { action: "input->search-form#search" }, class: "block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:max-w-xs sm:text-sm" %>
19
+ <%%= f.text_field :<%= col.name %>_cont, placeholder: I18n.t('bo.<%= class_name.underscore %>.attributes.<%= col.name %>'), data: { action: "input->tybo--search-form#search" }, class: "block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:max-w-xs sm:text-sm" %>
20
20
  <%% end %>
21
21
  <%- elsif col.type ==:integer -%>
22
22
  <%%= render(Forms::SearchInputComponent.new(label: I18n.t('bo.<%= class_name.underscore %>.attributes.<%= col.name %>'))) do %>
23
- <%%= f.number_field :<%= col.name %>_eq, placeholder: I18n.t('bo.<%= class_name.underscore %>.attributes.<%= col.name %>'), data: { action: "input->search-form#search" }, class: "block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:max-w-xs sm:text-sm" %>
23
+ <%%= f.number_field :<%= col.name %>_eq, placeholder: I18n.t('bo.<%= class_name.underscore %>.attributes.<%= col.name %>'), data: { action: "input->tybo--search-form#search" }, class: "block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:max-w-xs sm:text-sm" %>
24
24
  <%% end %>
25
25
  <%- elsif col.type == :boolean -%>
26
26
  <div class="pt-5 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4">
@@ -31,23 +31,23 @@
31
31
  <span class="inline-flex rounded-md shadow-sm isolate">
32
32
  <button type="button"
33
33
  data-target-id='q_<%= col.name %>_eq'
34
- data-action="click->search-form#setBooleanField"
34
+ data-action="click->tybo--search-form#setBooleanField"
35
35
  class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 focus:z-10 focus:border-tybo-500 focus:outline-none focus:ring-1 focus:ring-tybo-500">
36
36
  -
37
37
  </button>
38
38
  <button type="button"
39
- data-action="click->search-form#setBooleanField"
39
+ data-action="click->tybo--search-form#setBooleanField"
40
40
  data-value='true'
41
41
  data-target-id='q_<%= col.name %>_eq'
42
42
  class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 focus:z-10 focus:border-tybo-500 focus:outline-none focus:ring-1 focus:ring-tybo-500">ON
43
43
  </button>
44
44
  <button type="button"
45
- data-action="click->search-form#setBooleanField"
45
+ data-action="click->tybo--search-form#setBooleanField"
46
46
  data-value='false'
47
47
  data-target-id='q_<%= col.name %>_eq'
48
48
  class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 focus:z-10 focus:border-tybo-500 focus:outline-none focus:ring-1 focus:ring-tybo-500">OFF</button>
49
49
  </span>
50
- <%%= f.hidden_field :<%= col.name %>_eq, value: nil, data: { action: "change->search-form#search"}%>
50
+ <%%= f.hidden_field :<%= col.name %>_eq, value: nil, data: { action: "change->tybo--search-form#search"}%>
51
51
  </div>
52
52
  </div>
53
53
  <%- elsif col.type == :datetime || col.type == :date -%>
@@ -66,7 +66,7 @@
66
66
  <%%= I18n.t('bo.<%=association.klass.name.underscore%>.one') %>
67
67
  </label>
68
68
  <div class="mt-1 sm:col-span-2 sm:mt-0">
69
- <%%= f.select :<%= association.foreign_key %>_eq, <%=association.klass.name.constantize%>.all.map { |value| [value.<%=bo_model_title(association.class_name.constantize)%>, value.id] }, { include_blank: true }, { class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:text-sm", data: { action: "input->search-form#search" } } %>
69
+ <%%= f.select :<%= association.foreign_key %>_eq, <%=association.klass.name.constantize%>.all.map { |value| [value.<%=bo_model_title(association.class_name.constantize)%>, value.id] }, { include_blank: true }, { class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-tybo-500 focus:ring-tybo-500 sm:text-sm", data: { action: "input->tybo--search-form#search" } } %>
70
70
  </div>
71
71
  </div>
72
72
  <%- end -%>
@@ -6,7 +6,7 @@ class BoNamespaceGenerator < Rails::Generators::NamedBase
6
6
 
7
7
  def create_bo_namespace_files
8
8
  run "bundle exec rails g devise #{file_name.capitalize}"
9
- run 'bundle exec rails db:migrate'
9
+ rails_command "db:migrate"
10
10
  create_routes_and_views
11
11
  template 'admin.html.erb', File.join('app/views/layouts/', "#{singular_name}.html.erb")
12
12
  template 'admin_controller.rb', File.join('app/controllers/', "#{singular_name}_controller.rb")
@@ -5,10 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <%%= csrf_meta_tags %>
7
7
  <%%= csp_meta_tag %>
8
- <%%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
9
- <%%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
10
- <%%= javascript_importmap_tags %>
11
- <%%= hotwire_livereload_tags if Rails.env.development? %>
8
+ <%%= stylesheet_link_tag "tybo_admin", "data-turbo-track": "reload" %>
9
+ <%%= javascript_importmap_tags "application_tybo_admin" %>
12
10
  </head>
13
11
  <body class="h-full bg-home">
14
12
  <%% if <%= class_name.underscore %>_signed_in? %>
@@ -0,0 +1,20 @@
1
+ import "@hotwired/turbo-rails"
2
+ import { Application } from "@hotwired/stimulus"
3
+
4
+ import Attachments from "tybo/controllers/attachments_controller"
5
+ import Dropdown from "tybo/controllers/dropdown_controller"
6
+ import Flash from "tybo/controllers/flash_controller"
7
+ import SearchForm from "tybo/controllers/search_form_controller"
8
+ import TsSearch from "tybo/controllers/ts/search_controller"
9
+ import TsSelect from "tybo/controllers/ts/select_controller"
10
+ import Sidebar from "tybo/controllers/sidebar_controller"
11
+
12
+ const application = Application.start()
13
+
14
+ application.register("tybo--attachments", Attachments)
15
+ application.register("tybo--dropdown", Dropdown)
16
+ application.register("tybo--flash", Flash)
17
+ application.register("tybo--search-form", SearchForm)
18
+ application.register("tybo--ts--search", TsSearch)
19
+ application.register("tybo--ts--select", TsSelect)
20
+ application.register("tybo--sidebar", Sidebar)
@@ -0,0 +1,112 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+ @import 'actiontext.css';
5
+
6
+ /* pagination */
7
+ .pagy-nav,
8
+ .pagy-nav-js {
9
+ @apply flex space-x-2;
10
+ }
11
+
12
+ .pagy-nav .page a,
13
+ .pagy-nav .page.active,
14
+ .pagy-nav .page.prev.disabled,
15
+ .pagy-nav .page.next.disabled,
16
+ .pagy-nav-js .page a,
17
+ .pagy-nav-js .page.active,
18
+ .pagy-nav-js .page.prev.disabled,
19
+ .pagy-nav-js .page.next.disabled {
20
+ @apply block rounded-lg px-3 py-1 text-sm text-tybo-500 font-semibold bg-tybo-200 shadow-md;
21
+
22
+ &:hover {
23
+ @apply bg-tybo-300;
24
+ }
25
+
26
+ &:active {
27
+ @apply bg-tybo-400 text-white;
28
+ }
29
+ }
30
+
31
+ .pagy-nav .page.prev.disabled,
32
+ .pagy-nav .page.next.disabled,
33
+ .pagy-nav-js .page.prev.disabled,
34
+ .pagy-nav-js .page.next.disabled {
35
+ @apply text-tybo-400 cursor-default;
36
+
37
+ &:hover {
38
+ @apply text-tybo-400 bg-tybo-200;
39
+ }
40
+
41
+ &:active {
42
+ @apply text-tybo-400 bg-tybo-200;
43
+ }
44
+ }
45
+
46
+ .pagy-nav .page.active,
47
+ .pagy-nav-js .page.active {
48
+ @apply text-white cursor-default bg-tybo-400;
49
+
50
+ &:hover {
51
+ @apply text-white bg-tybo-400;
52
+ }
53
+
54
+ &:active {
55
+ @apply bg-tybo-400 text-white;
56
+ }
57
+ }
58
+
59
+
60
+ .pagy-combo-nav-js {
61
+ @apply flex max-w-max rounded-full px-3 py-1 text-sm text-tybo-500 font-semibold bg-tybo-200 shadow-md;
62
+ }
63
+
64
+ .pagy-combo-nav-js .pagy-combo-input {
65
+ @apply bg-white px-2 rounded-sm
66
+ }
67
+
68
+ .pagy-combo-nav-js .page.prev,
69
+ .pagy-combo-nav-js .page.next {
70
+ &:hover {
71
+ @apply text-tybo-800;
72
+ }
73
+
74
+ &:active {
75
+ @apply text-tybo-800;
76
+ }
77
+ }
78
+
79
+ .pagy-combo-nav-js .page.prev.disabled,
80
+ .pagy-combo-nav-js .page.next.disabled {
81
+ @apply text-tybo-400 cursor-default;
82
+ }
83
+
84
+ /* tom-select */
85
+ ts-wrapper {
86
+ @apply w-full !ml-0;
87
+ }
88
+
89
+ .ts-control {
90
+ @apply shadow-sm rounded-lg my-5 border-tybo-300 bg-white py-2 px-3 text-base;
91
+ }
92
+
93
+ .ts-dropdown {
94
+ @apply rounded-md border border-solid border-t border-tybo-300 text-base;
95
+ }
96
+
97
+ .ts-dropdown [data-selectable].option:first-child {
98
+ @apply rounded-t-lg;
99
+ }
100
+
101
+ .ts-dropdown [data-selectable].option:last-child {
102
+ @apply rounded-b-lg;
103
+ }
104
+
105
+ .ts-dropdown .create:hover,
106
+ .ts-dropdown .option:hover {
107
+ @apply bg-sky-50 text-sky-900;
108
+ }
109
+
110
+ .ts-dropdown .active {
111
+ @apply bg-tybo-100 text-tybo-900;
112
+ }
@@ -4,6 +4,6 @@ Tybo.configure do |config|
4
4
  # customise logo and cover url
5
5
  # should be an external url or image should be present in (app/assets/images)
6
6
  config.logo_url = 'logo.svg'
7
- config.nav_logo_url = 'nav_logo.png'
7
+ config.nav_logo_url = 'logo.svg'
8
8
  config.cover_url = 'cover.svg'
9
9
  end
@@ -11,12 +11,11 @@ class TyboInstallGenerator < Rails::Generators::Base
11
11
  gem 'simple_form-tailwind', '~> 0.1.1' unless Bundler.locked_gems.specs.any? { |gem| gem.name == 'simple_form-tailwind' }
12
12
  gem 'action_policy', '~> 0.7.5' unless Bundler.locked_gems.specs.any? { |gem| gem.name == 'actionpolicy' }
13
13
  run 'bundle install'
14
- run "rails tailwindcss:install"
15
14
  end
16
15
 
17
16
  def create_configuration_files
18
17
  create_base_translation_files
19
- template 'application.tailwind.css', File.join('app/assets/stylesheets/application.tailwind.css'), force: true
18
+ template 'tybo_admin.tailwind.css', File.join('app/assets/stylesheets/tybo_admin.tailwind.css')
20
19
  template 'tailwind.config.js', File.join('config/tailwind.config.js'), force: true
21
20
  template 'tom-select.css', File.join('app/assets/stylesheets/tom-select.css')
22
21
  template 'simple_form_tailwind.rb', File.join('config/initializers/simple_form_tailwind.rb')
@@ -24,8 +23,23 @@ class TyboInstallGenerator < Rails::Generators::Base
24
23
  end
25
24
 
26
25
  def pin_js_dependencies
27
- run "./bin/importmap pin tom-select --download"
28
- run "./bin/importmap pin @tymate/tybo_js"
26
+ inject_into_file 'config/importmap.rb', before: /\z/ do
27
+ <<~RUBY
28
+
29
+ pin "tom-select", to: "https://esm.sh/tom-select"
30
+ pin "@rails/request.js", to: "https://esm.sh/@rails/request.js"
31
+ RUBY
32
+ end
33
+ end
34
+
35
+ def setup_puma_plugin
36
+ inject_into_file 'config/puma.rb', before: /\z/ do
37
+ "\nplugin :tybo if ENV.fetch(\"RAILS_ENV\", \"development\") == \"development\"\n"
38
+ end
39
+ end
40
+
41
+ def build_css
42
+ rake "tybo:build_css"
29
43
  end
30
44
 
31
45
  def create_routes
@@ -37,13 +51,19 @@ class TyboInstallGenerator < Rails::Generators::Base
37
51
  run 'rails g bo_namespace Administrator'
38
52
  end
39
53
 
54
+ def copy_javascript_controllers
55
+ js_source = Tybo::Engine.root.join("app/assets/javascripts/tybo/controllers")
56
+ js_dest = "app/javascript/tybo/controllers"
57
+
58
+ directory js_source.to_s, js_dest
59
+ end
60
+
40
61
  def add_javascript_controllers
41
- inject_into_file 'app/javascript/controllers/application.js', after: "const application = Application.start()\n" do
42
- "import { Dropdown, Flash, SearchForm, TsSearch, TsSelect, Sidebar } from \"@tymate/tybo_js\"\n"
43
- end
62
+ template 'application_tybo_admin.js', 'app/javascript/tybo/application_tybo_admin.js'
44
63
 
45
- inject_into_file 'app/javascript/controllers/application.js', before: "export { application }" do
46
- "application.register('dropdown', Dropdown)\napplication.register('flash', Flash)\napplication.register('search-form', SearchForm)\napplication.register('ts--search', TsSearch)\napplication.register('ts--select', TsSelect)\napplication.register('sidebar', Sidebar)\n"
64
+ inject_into_file 'config/importmap.rb', before: /\z/ do
65
+ "\npin \"application_tybo_admin\", to: \"tybo/application_tybo_admin.js\"\n" \
66
+ "pin_all_from \"app/javascript/tybo/controllers\", under: \"tybo/controllers\"\n"
47
67
  end
48
68
  end
49
69
 
@@ -1,15 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ FR_DATE_TRANSLATIONS = {
4
+ 'date' => {
5
+ 'abbr_day_names' => %w[Dim Lun Mar Mer Jeu Ven Sam],
6
+ 'day_names' => %w[Dimanche Lundi Mardi Mercredi Jeudi Vendredi Samedi],
7
+ 'abbr_month_names' => [nil, 'Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'],
8
+ 'month_names' => [nil, 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
9
+ 'formats' => { 'default' => '%d/%m/%Y', 'short' => '%e %b', 'long' => '%e %B %Y' }
10
+ },
11
+ 'time' => {
12
+ 'am' => 'am',
13
+ 'pm' => 'pm',
14
+ 'formats' => { 'default' => '%A %d %B %Y %H:%M:%S %z', 'short' => '%d %b %H:%M', 'long' => '%A %d %B %Y %H:%M' }
15
+ },
16
+ 'datetime' => {
17
+ 'distance_in_words' => {
18
+ 'half_a_minute' => 'une demi-minute',
19
+ 'less_than_x_seconds' => { 'one' => "moins d'une seconde", 'other' => 'moins de %{count} secondes' },
20
+ 'x_seconds' => { 'one' => '1 seconde', 'other' => '%{count} secondes' },
21
+ 'less_than_x_minutes' => { 'one' => "moins d'une minute", 'other' => 'moins de %{count} minutes' },
22
+ 'x_minutes' => { 'one' => '1 minute', 'other' => '%{count} minutes' },
23
+ 'about_x_hours' => { 'one' => 'environ 1 heure', 'other' => 'environ %{count} heures' },
24
+ 'x_days' => { 'one' => '1 jour', 'other' => '%{count} jours' },
25
+ 'about_x_months' => { 'one' => 'environ 1 mois', 'other' => 'environ %{count} mois' },
26
+ 'x_months' => { 'one' => '1 mois', 'other' => '%{count} mois' },
27
+ 'about_x_years' => { 'one' => 'environ 1 an', 'other' => 'environ %{count} ans' },
28
+ 'over_x_years' => { 'one' => "plus d'un an", 'other' => 'plus de %{count} ans' },
29
+ 'almost_x_years' => { 'one' => 'presque 1 an', 'other' => 'presque %{count} ans' }
30
+ }
31
+ }
32
+ }.freeze
33
+
3
34
  def create_base_translation_files
4
35
  %w[en fr].each do |locale|
5
36
  locale_file = "config/locales/bo.#{locale}.yml"
37
+ extra = locale == 'fr' ? FR_DATE_TRANSLATIONS : {}
6
38
  File.write(locale_file, {
7
- locale => {
39
+ locale => extra.merge({
8
40
  'bo' => {
9
41
  'filters' => find_existing_translation('filters', locale),
10
42
  'show' => find_existing_translation('show', locale),
11
43
  'to' => find_existing_translation('to', locale),
12
44
  'export_btn' => find_existing_translation('export_btn', locale),
45
+ 'add_ressource_btn' => find_existing_translation('add_ressource_btn', locale),
13
46
  'confirm_delete' => find_existing_translation('confirm_delete', locale),
14
47
  'record' => {
15
48
  'created' => find_existing_translation('created', locale),
@@ -38,7 +71,7 @@
38
71
  'save' => find_existing_translation('save', locale),
39
72
  }
40
73
  }
41
- }
74
+ })
42
75
  }.to_yaml)
43
76
  end
44
77
  end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TyboUpgradeGenerator < Rails::Generators::Base
4
+ # Reuse the install templates (application_tybo_admin.js, tybo_admin.tailwind.css)
5
+ source_root File.expand_path("../tybo_install/templates", __dir__)
6
+
7
+ TYBO_CONTROLLERS = %w[
8
+ attachments_controller
9
+ dropdown_controller
10
+ flash_controller
11
+ search_form_controller
12
+ sidebar_controller
13
+ ts/search_controller
14
+ ts/select_controller
15
+ ].freeze
16
+
17
+ def upgrade_css
18
+ say_status :upgrade, "CSS → tybo_admin.css", :blue
19
+
20
+ unless File.exist?("app/assets/stylesheets/tybo_admin.tailwind.css")
21
+ if File.exist?("app/assets/stylesheets/application.tailwind.css")
22
+ FileUtils.cp("app/assets/stylesheets/application.tailwind.css",
23
+ "app/assets/stylesheets/tybo_admin.tailwind.css")
24
+ say_status :info, "Copied application.tailwind.css → tybo_admin.tailwind.css", :green
25
+ else
26
+ template "tybo_admin.tailwind.css", "app/assets/stylesheets/tybo_admin.tailwind.css"
27
+ end
28
+ end
29
+
30
+ unless File.read("config/puma.rb").include?("plugin :tybo")
31
+ inject_into_file "config/puma.rb", before: /\z/ do
32
+ "\nplugin :tybo if ENV.fetch(\"RAILS_ENV\", \"development\") == \"development\"\n"
33
+ end
34
+ end
35
+ end
36
+
37
+ def upgrade_layouts
38
+ say_status :upgrade, "Admin layouts", :blue
39
+
40
+ Dir.glob("app/views/layouts/*.html.erb").each do |layout|
41
+ content = File.read(layout)
42
+ next unless content.include?("tailwind") || content.include?("@tymate")
43
+
44
+ # stylesheet : retire "application" + remplace "tailwind"
45
+ gsub_file layout,
46
+ /[ \t]*<%=[ \t]*stylesheet_link_tag[ \t]+"application"[^%]*%>\n/, ""
47
+ gsub_file layout,
48
+ /stylesheet_link_tag "tailwind"[^%]*%>/,
49
+ 'stylesheet_link_tag "tybo_admin", "data-turbo-track": "reload" %>'
50
+ gsub_file layout,
51
+ /, "inter-font"/, ""
52
+
53
+ # importmap
54
+ gsub_file layout,
55
+ /javascript_importmap_tags(?!\s+"application_tybo_admin")/,
56
+ 'javascript_importmap_tags "application_tybo_admin"'
57
+ end
58
+ end
59
+
60
+ def upgrade_importmap
61
+ say_status :upgrade, "config/importmap.rb", :blue
62
+
63
+ # Remove old pins
64
+ gsub_file "config/importmap.rb",
65
+ /.*pin "@tymate\/tybo_js".*\n/, ""
66
+ gsub_file "config/importmap.rb",
67
+ /.*pin "tom-select".*\n/, ""
68
+ gsub_file "config/importmap.rb",
69
+ /.*pin "@rails\/request\.js".*\n/, ""
70
+ gsub_file "config/importmap.rb",
71
+ /.*pin_all_from "app\/javascript\/tybo\/controllers".*\n/, ""
72
+ gsub_file "config/importmap.rb",
73
+ /.*pin "application_tybo_admin".*\n/, ""
74
+
75
+ inject_into_file "config/importmap.rb", before: /\z/ do
76
+ <<~RUBY
77
+
78
+ pin "application_tybo_admin", to: "tybo/application_tybo_admin.js"
79
+ pin_all_from "app/javascript/tybo/controllers", under: "tybo/controllers"
80
+ pin "tom-select", to: "https://esm.sh/tom-select"
81
+ pin "@rails/request.js", to: "https://esm.sh/@rails/request.js"
82
+ RUBY
83
+ end
84
+ end
85
+
86
+ def upgrade_javascript
87
+ say_status :upgrade, "JavaScript entry point", :blue
88
+
89
+ # Copy JS controllers from engine into the host app
90
+ js_source = Tybo::Engine.root.join("app/assets/javascripts/tybo/controllers")
91
+ js_dest = "app/javascript/tybo/controllers"
92
+ directory js_source.to_s, js_dest, force: false
93
+ say_status :info, "Copied Tybo controllers to #{js_dest} (existing files kept)", :green
94
+
95
+ # Create application_tybo_admin.js (entry point for admin)
96
+ template "application_tybo_admin.js", "app/javascript/tybo/application_tybo_admin.js"
97
+
98
+ # In index.js, comment out the tybo controller exports that are now
99
+ # handled by application_tybo_admin.js — without deleting anything
100
+ old_index = "app/javascript/tybo/controllers/index.js"
101
+ if File.exist?(old_index)
102
+ content = File.read(old_index, encoding: "UTF-8")
103
+ TYBO_CONTROLLERS.each do |ctrl|
104
+ basename = File.basename(ctrl)
105
+ content = content.gsub(
106
+ /^(export \{ default as \w+ \} from ".*#{Regexp.escape(basename)}.*")/,
107
+ '// [tybo 0.7] registered in application_tybo_admin.js — \1'
108
+ )
109
+ end
110
+ File.write(old_index, content, encoding: "UTF-8")
111
+ say_status :info, "Commented out tybo exports in index.js", :yellow
112
+ end
113
+
114
+ # Comment out (not delete) old tybo registrations from application.js
115
+ app_js = "app/javascript/controllers/application.js"
116
+ if File.exist?(app_js)
117
+ content = File.read(app_js, encoding: "UTF-8")
118
+ if content.include?("@tymate/tybo_js")
119
+ content = content.gsub(
120
+ /^(import \{[^}]+\} from "@tymate\/tybo_js")/,
121
+ '// [tybo 0.7] moved to application_tybo_admin.js — \1'
122
+ )
123
+ %w[dropdown flash search-form ts--search ts--select sidebar].each do |name|
124
+ content = content.gsub(
125
+ /^(application\.register\('#{Regexp.escape(name)}'.*)/,
126
+ '// [tybo 0.7] moved to application_tybo_admin.js — \1'
127
+ )
128
+ end
129
+ File.write(app_js, content, encoding: "UTF-8")
130
+ say_status :info, "Commented out @tymate/tybo_js lines in application.js", :yellow
131
+ end
132
+ end
133
+ end
134
+
135
+ def upgrade_data_controllers
136
+ say_status :upgrade, "Stimulus data-controller attributes", :blue
137
+
138
+ views = Dir.glob("app/views/**/*.html.erb") + Dir.glob("app/components/**/*.html.erb")
139
+
140
+ views.each do |file|
141
+ content = File.read(file, encoding: "UTF-8")
142
+
143
+ next unless content.match?(/
144
+ data-controller="(sidebar|flash|dropdown|search-form|ts--|attachments)" |
145
+ ->sidebar\# | ->flash\# | ->dropdown\# | ->search-form\# |
146
+ ->ts--select\# | ->ts--search\# | ->attachments\# |
147
+ data-sidebar-target | data-dropdown-target
148
+ /x)
149
+
150
+ {
151
+ 'data-controller="sidebar"' => 'data-controller="tybo--sidebar"',
152
+ 'data-controller="flash"' => 'data-controller="tybo--flash"',
153
+ 'data-controller="dropdown"' => 'data-controller="tybo--dropdown"',
154
+ 'data-controller="search-form"' => 'data-controller="tybo--search-form"',
155
+ 'data-controller="ts--select"' => 'data-controller="tybo--ts--select"',
156
+ 'data-controller="ts--search"' => 'data-controller="tybo--ts--search"',
157
+ 'data-controller="attachments"' => 'data-controller="tybo--attachments"',
158
+ 'data-sidebar-target' => 'data-tybo--sidebar-target',
159
+ 'data-dropdown-target' => 'data-tybo--dropdown-target',
160
+ '->sidebar#' => '->tybo--sidebar#',
161
+ '->flash#' => '->tybo--flash#',
162
+ '->dropdown#' => '->tybo--dropdown#',
163
+ '->search-form#' => '->tybo--search-form#',
164
+ '->ts--select#' => '->tybo--ts--select#',
165
+ '->ts--search#' => '->tybo--ts--search#',
166
+ '->attachments#' => '->tybo--attachments#',
167
+ 'search_form_target:' => '"tybo--search-form-target":',
168
+ "controller: 'ts--select'" => "controller: 'tybo--ts--select'",
169
+ 'controller: "search-form"' => 'controller: "tybo--search-form"'
170
+ }.each { |from, to| content = content.gsub(from, to) }
171
+
172
+ File.write(file, content, encoding: "UTF-8")
173
+ say_status :gsub, file, :green
174
+ end
175
+ end
176
+
177
+ def build_css
178
+ say_status :upgrade, "Building tybo_admin.css", :blue
179
+ rake "tybo:build_css"
180
+ end
181
+
182
+ def done
183
+ say ""
184
+ say "✓ Tybo upgraded to 0.7.x", :green
185
+ say ""
186
+ say "Files to review:", :yellow
187
+ say " app/javascript/tybo/controllers/ (controllers copied from engine)", :yellow
188
+ say " app/javascript/tybo/controllers/index.js (exports commented out)", :yellow
189
+ say " app/javascript/controllers/application.js (registrations commented out)", :yellow
190
+ say ""
191
+ say "Once reviewed, you can delete the commented lines safely."
192
+ end
193
+ end
@@ -0,0 +1,36 @@
1
+ require "tailwindcss/ruby"
2
+
3
+ Puma::Plugin.create do
4
+ def start(launcher)
5
+ in_background do
6
+ rails_root = defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd)
7
+
8
+ input = rails_root.join("app/assets/stylesheets/tybo_admin.tailwind.css")
9
+ output = rails_root.join("app/assets/builds/tybo_admin.css")
10
+ config = rails_root.join("config/tailwind.config.js")
11
+
12
+ unless input.exist?
13
+ launcher.log_writer.write("Tybo: #{input} not found, skipping CSS watch\n")
14
+ next
15
+ end
16
+
17
+ command = [
18
+ Tailwindcss::Ruby.executable,
19
+ "-i", input.to_s,
20
+ "-o", output.to_s,
21
+ "-c", config.to_s,
22
+ "-w"
23
+ ]
24
+
25
+ pid = spawn(*command)
26
+ launcher.events.on_stopped { Process.kill(:INT, pid) rescue nil }
27
+
28
+ begin
29
+ Process.wait(pid)
30
+ rescue Interrupt
31
+ Process.kill(:INT, pid) rescue nil
32
+ Process.wait(pid)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ require "tailwindcss/ruby"
2
+
3
+ namespace :tybo do
4
+ desc "Build Tybo Admin CSS"
5
+ task build_css: :environment do
6
+ input = Rails.root.join("app/assets/stylesheets/tybo_admin.tailwind.css")
7
+ output = Rails.root.join("app/assets/builds/tybo_admin.css")
8
+ config = Rails.root.join("config/tailwind.config.js")
9
+
10
+ unless input.exist?
11
+ warn "WARNING: #{input} not found, skipping tybo CSS build"
12
+ next
13
+ end
14
+
15
+ command = [
16
+ Tailwindcss::Ruby.executable,
17
+ "-i", input.to_s,
18
+ "-o", output.to_s,
19
+ "-c", config.to_s,
20
+ ]
21
+ command << "--minify" unless ENV["TAILWINDCSS_DEBUG"].present?
22
+
23
+ system(*command, exception: true)
24
+ end
25
+
26
+ desc "Watch and build Tybo Admin CSS on file changes"
27
+ task watch_css: :environment do
28
+ input = Rails.root.join("app/assets/stylesheets/tybo_admin.tailwind.css")
29
+ output = Rails.root.join("app/assets/builds/tybo_admin.css")
30
+ config = Rails.root.join("config/tailwind.config.js")
31
+
32
+ command = [
33
+ Tailwindcss::Ruby.executable,
34
+ "-i", input.to_s,
35
+ "-o", output.to_s,
36
+ "-c", config.to_s,
37
+ "-w"
38
+ ]
39
+
40
+ system(*command)
41
+ rescue Interrupt
42
+ # silent exit on Ctrl-C
43
+ end
44
+ end
45
+
46
+ Rake::Task["assets:precompile"].enhance(["tybo:build_css"])
data/lib/tybo/engine.rb CHANGED
@@ -5,6 +5,10 @@ require 'view_component'
5
5
 
6
6
  module Tybo
7
7
  class Engine < ::Rails::Engine
8
+ rake_tasks do
9
+ load "#{root}/lib/tasks/tybo.rake"
10
+ end
11
+
8
12
  # Devise
9
13
  config.to_prepare do
10
14
  Devise::ConfirmationsController.layout "devise_admin"
data/lib/tybo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tybo
2
- VERSION = '0.6.1'
2
+ VERSION = '0.7.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tybo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Delpierre
@@ -139,8 +139,19 @@ files:
139
139
  - README.md
140
140
  - Rakefile
141
141
  - app/assets/config/tybo_manifest.js
142
+ - app/assets/images/cover.svg
143
+ - app/assets/images/logo.svg
142
144
  - app/assets/images/tybo/cover.svg
143
145
  - app/assets/images/tybo/logo.svg
146
+ - app/assets/javascripts/tybo/controllers/attachments_controller.js
147
+ - app/assets/javascripts/tybo/controllers/dropdown_controller.js
148
+ - app/assets/javascripts/tybo/controllers/flash_controller.js
149
+ - app/assets/javascripts/tybo/controllers/index.js
150
+ - app/assets/javascripts/tybo/controllers/questions_controller.js
151
+ - app/assets/javascripts/tybo/controllers/search_form_controller.js
152
+ - app/assets/javascripts/tybo/controllers/sidebar_controller.js
153
+ - app/assets/javascripts/tybo/controllers/ts/search_controller.js
154
+ - app/assets/javascripts/tybo/controllers/ts/select_controller.js
144
155
  - app/assets/stylesheets/tybo/application.css
145
156
  - app/components/attachment_card_component.html.erb
146
157
  - app/components/attachment_card_component.rb
@@ -260,6 +271,7 @@ files:
260
271
  - app/views/layouts/tybo/application.html.erb
261
272
  - app/views/login/home.html.erb
262
273
  - app/views/shared/_pagination.html.erb
274
+ - config/importmap.rb
263
275
  - config/initializers/devise.rb
264
276
  - config/initializers/pagy.rb
265
277
  - config/initializers/ransack.rb
@@ -291,14 +303,19 @@ files:
291
303
  - lib/generators/bo_namespace/utils/files/fr.json
292
304
  - lib/generators/bo_namespace/utils/translations.rb
293
305
  - lib/generators/tybo_install/templates/application.tailwind.css
306
+ - lib/generators/tybo_install/templates/application_tybo_admin.js
294
307
  - lib/generators/tybo_install/templates/simple_form_tailwind.rb
295
308
  - lib/generators/tybo_install/templates/tailwind.config.js
296
309
  - lib/generators/tybo_install/templates/tom-select.css
310
+ - lib/generators/tybo_install/templates/tybo_admin.tailwind.css
297
311
  - lib/generators/tybo_install/templates/tybo_config.rb
298
312
  - lib/generators/tybo_install/tybo_install_generator.rb
299
313
  - lib/generators/tybo_install/utils/files/en.json
300
314
  - lib/generators/tybo_install/utils/files/fr.json
301
315
  - lib/generators/tybo_install/utils/translations.rb
316
+ - lib/generators/tybo_upgrade/tybo_upgrade_generator.rb
317
+ - lib/puma/plugin/tybo.rb
318
+ - lib/tasks/tybo.rake
302
319
  - lib/tybo.rb
303
320
  - lib/tybo/configuration.rb
304
321
  - lib/tybo/engine.rb