bullet_train 1.0.92 → 1.0.96
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/bullet-train.js +1 -1
- data/app/assets/javascripts/bullet-train.js.map +1 -1
- data/app/controllers/concerns/account/invitations/controller_base.rb +13 -15
- data/app/controllers/sessions_controller.rb +11 -0
- data/app/javascript/controllers/bulk_action_form_controller.js +1 -1
- data/app/javascript/controllers/bulk_actions_controller.js +1 -1
- data/app/mailers/concerns/mailers/base.rb +1 -0
- data/app/models/concerns/memberships/base.rb +5 -1
- data/app/models/concerns/users/base.rb +1 -2
- data/app/views/account/memberships/show.html.erb +3 -1
- data/app/views/account/onboarding/user_details/edit.html.erb +1 -1
- data/config/locales/en/framework_packages.yml +43 -0
- data/config/locales/en/invitations.en.yml +1 -0
- data/config/locales/en/teams.en.yml +1 -0
- data/docs/action-models.md +4 -4
- data/docs/field-partials/super-select.md +11 -0
- data/docs/indirection.md +26 -1
- data/docs/onboarding.md +10 -10
- data/docs/super-scaffolding/sortable.md +137 -0
- data/docs/super-scaffolding.md +1 -0
- data/lib/bullet_train/resolver.rb +41 -32
- data/lib/bullet_train/version.rb +1 -1
- data/lib/bullet_train.rb +2 -2
- data/lib/tasks/bullet_train_tasks.rake +42 -75
- metadata +33 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e32e536643be032d267207a75c56fabf21aebe72287282c6b50ada2f3493df88
|
4
|
+
data.tar.gz: 49a9a73f61d045595ef8259953b3066d442c591b5bb15bbc1dee9998e8d560eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d97ef875b0d800b5cb807d4e1257b2ec7564e1e08f85e437d4d75437c4b5a6b4ef2f58aa380c5ce2b674dd92ecd2f58a910a68811b61a6847f7933219409823
|
7
|
+
data.tar.gz: 56cb003bdccdc8baad20d91be3b3bb0bbfda1cb3b1bb50759c91c1d062b9a357e98db79cf55583bedddd8480629ea4f12aeb5fc727e1f7bc79f6729578827b7a
|
@@ -1,2 +1,2 @@
|
|
1
|
-
import{
|
1
|
+
import{Controller as e}from"@hotwired/stimulus";function t(e){const t=(e.match(/^(?:\.\/)?(.+)(?:[_-]controller\..+?)$/)||[])[1];if(t)return t.replace(/_/g,"-").replace(/\//g,"--")}class l extends e{connect(){this.updateAvailability()}updateFormAndSubmit(e){return this.recreateIdsHiddenFields(),this.createOrUpdateAllField(),!0}updateIds(e){var t;null!=e&&null!=(t=e.detail)&&t.ids&&(this.idsValue=e.detail.ids,this.allValue=e.detail.all),this.updateAvailability(),this.updateButtonLabel()}updateAvailability(){this.element.classList.toggle(this.hiddenClass,0===this.idsValue.length)}updateButtonLabel(){let e=this.buttonIfAllValue;this.idsValue.length&&!1===this.allValue&&(e=this.buttonIfIdsValue.replace("{num}",this.idsValue.length)),"INPUT"===this.buttonTarget.tagName?this.buttonTarget.value=e:this.buttonTarget.textContent=e}recreateIdsHiddenFields(){this.removeIdsHiddenFields(),this.createIdsHiddenFields()}removeIdsHiddenFields(){this.idsHiddenFieldTargets.forEach(e=>{this.element.removeChild(e)})}createIdsHiddenFields(){this.idsValue.forEach(e=>{let t=document.createElement("input");t.type="hidden",t.name=this.objectNameValue+"["+this.idsFieldNameValue+"][]",t.value=e,this.element.appendChild(t)})}createOrUpdateAllField(){this.hasAllHiddenFieldTarget?this.allHiddenFieldTarget.value=this.allValue?"true":"false":this.createAllField()}createAllField(){let e=document.createElement("input");e.type="hidden",e.name=this.objectNameValue+"["+this.allFieldNameValue+"]",e.value=this.allValue?"true":"false",this.element.appendChild(e)}}l.targets=["button","idsHiddenField","allHiddenField"],l.classes=["hidden"],l.values={buttonIfAll:String,buttonIfIds:String,ids:Array,all:Boolean,objectName:String,idsFieldName:String,allFieldName:String};class s extends e{connect(){this.element.classList.add(this.selectableAvailableClass)}toggleSelectable(){this.selectableValue=!this.selectableValue}updateSelectedIds(){this.updateActions(),this.updateSelectAllCheckbox()}updateActions(){this.actionTargets.forEach(e=>{e.dispatchEvent(new CustomEvent("update-ids",{detail:{ids:this.selectedIds,all:this.allSelected}}))})}selectAllOrNone(e){this.allSelected?this.selectNone():this.selectAll(),this.updateSelectAllCheckbox()}selectAll(){this.checkboxTargets.forEach(e=>{e.checked=!0}),this.updateActions()}selectNone(){this.checkboxTargets.forEach(e=>{e.checked=!1}),this.updateActions()}updateSelectAllCheckbox(){let e=this.selectAllCheckboxTarget,t=this.selectAllLabelTarget;this.allSelected?(e.checked=!0,e.indeterminate=!1,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!0}}))):this.selectedIds.length>0?(e.indeterminate=!0,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!1}}))):(e.checked=!1,e.indeterminate=!1,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!1}})))}selectableValueChanged(){this.element.classList.toggle(this.selectableClass,this.selectableValue),this.updateToggleLabel()}updateToggleLabel(){this.selectableToggleTarget.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:this.selectableValue}}))}get selectedIds(){let e=[];return this.checkboxTargets.forEach(t=>{t.checked&&e.push(t.value)}),e}get allSelected(){return this.selectedIds.length===this.checkboxTargets.length}}s.targets=["checkbox","selectAllCheckbox","action","selectableToggle","selectAllLabel"],s.classes=["selectableAvailable","selectable"],s.values={selectable:Boolean};class i extends e{copy(){this.inputTarget.value=this.sourceTarget.innerText,this.inputTarget.select(),document.execCommand("copy"),this.buttonTarget.innerHTML='<i id="copied" class="fas fa-check w-4 h-4 block text-green-600"></i>',setTimeout(function(){document.getElementById("copied").innerHTML='<i class="far fa-copy w-4 h-4 block text-gray-600"></i>'},1500)}}i.targets=["source","input","button"];class a extends e{constructor(){super(...arguments),this.removeTrailingNewlines=e=>{e.element.innerHTML.match(/<br><\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-10)+"</div>",this.removeTrailingNewlines(e))},this.removeTrailingWhitespace=e=>{e.element.innerHTML.match(/ <\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/ <\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-13)+"</div>",this.removeTrailingWhitespace(e))}}resetOnSuccess(e){e.detail.success&&e.target.reset()}stripTrix(){this.trixFieldTargets.forEach(e=>{this.removeTrailingNewlines(e.editor),this.removeTrailingWhitespace(e.editor),e.parentElement.querySelector("input").value=e.innerHTML})}submitOnReturn(e){if((e.metaKey||e.ctrlKey)&&13==e.keyCode){e.preventDefault();let t=e.target.closest("form");this.submitForm(t)}}submitForm(e){e.requestSubmit?e.requestSubmit():e.querySelector("[type=submit]").click()}}a.targets=["trixField","scroll"];class n extends e{toggle(){const e=this.isWrapperHidden?this.showEventNameValue:this.hideEventNameValue;this.isWrapperHidden&&this.showWrapper(),this.wrapperTarget.dispatchEvent(new CustomEvent(e))}get isWrapperHidden(){return this.wrapperTarget.classList.contains(this.hiddenClass)}showWrapper(){this.wrapperTarget.classList.remove(this.hiddenClass)}hideWrapper(){this.wrapperTarget.classList.add(this.hiddenClass)}}n.targets=["wrapper"],n.classes=["hidden"],n.values={showEventName:String,hideEventName:String};const r=[[l,"bulk_action_form_controller.js"],[s,"bulk_actions_controller.js"],[i,"clipboard_controller.js"],[a,"form_controller.js"],[n,"mobile_menu_controller.js"]].map(function(e){const l=e[0];return{identifier:t(e[1]),controllerConstructor:l}});document.addEventListener("turbo:load",()=>{navigator.userAgent.toLocaleLowerCase().includes("electron")&&document.body.classList.add("electron")});export{r as controllerDefinitions};
|
2
2
|
//# sourceMappingURL=bullet-train.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"bullet-train.js","sources":["../../javascript/controllers/bulk_action_form_controller.js","../../javascript/controllers/bulk_actions_controller.js","../../javascript/controllers/clipboard_controller.js","../../javascript/controllers/form_controller.js","../../javascript/controllers/mobile_menu_controller.js","../../javascript/controllers/index.js","../../javascript/electron/index.js"],"sourcesContent":["import { Controller } from 'stimulus'\n\nexport default class extends Controller {\n static targets = [ \"button\", \"idsHiddenField\", \"allHiddenField\" ]\n static classes = [ \"hidden\" ]\n static values = {\n buttonIfAll: String,\n buttonIfIds: String,\n ids: Array,\n all: Boolean,\n objectName: String,\n idsFieldName: String,\n allFieldName: String\n }\n\n connect() {\n this.updateAvailability()\n }\n\n updateFormAndSubmit(event) {\n this.recreateIdsHiddenFields()\n this.createOrUpdateAllField()\n return true\n }\n\n updateIds(event) {\n if (event?.detail?.ids) {\n this.idsValue = event.detail.ids\n this.allValue = event.detail.all\n }\n\n this.updateAvailability()\n this.updateButtonLabel()\n }\n\n updateAvailability() {\n this.element.classList.toggle(this.hiddenClass, this.idsValue.length === 0)\n }\n\n updateButtonLabel() {\n let label = this.buttonIfAllValue\n if (this.idsValue.length && this.allValue === false) {\n label = this.buttonIfIdsValue.replace('{num}', this.idsValue.length)\n }\n\n switch (this.buttonTarget.tagName) {\n case 'INPUT': this.buttonTarget.value = label; break;\n default: this.buttonTarget.textContent = label; break;\n }\n }\n\n recreateIdsHiddenFields() {\n this.removeIdsHiddenFields()\n this.createIdsHiddenFields()\n }\n\n removeIdsHiddenFields() {\n this.idsHiddenFieldTargets.forEach(field => {\n this.element.removeChild(field)\n })\n }\n\n createIdsHiddenFields() {\n this.idsValue.forEach(id => {\n let field = document.createElement('input')\n field.type = 'hidden'\n field.name = `${this.objectNameValue}[${this.idsFieldNameValue}][]`\n field.value = id\n this.element.appendChild(field)\n })\n }\n\n createOrUpdateAllField() {\n if (this.hasAllHiddenFieldTarget) {\n this.allHiddenFieldTarget.value = this.allValue? 'true': 'false'\n } else {\n this.createAllField()\n }\n }\n\n createAllField() {\n let field = document.createElement('input')\n field.type = 'hidden'\n field.name = `${this.objectNameValue}[${this.allFieldNameValue}]`\n field.value = this.allValue? 'true': 'false'\n this.element.appendChild(field)\n }\n}","import { Controller } from 'stimulus'\n\nexport default class extends Controller {\n static targets = [ \"checkbox\", \"selectAllCheckbox\", \"action\", \"selectableToggle\", \"selectAllLabel\" ]\n static classes = [ \"selectableAvailable\", \"selectable\" ]\n static values = {\n selectable: Boolean\n }\n\n connect() {\n this.element.classList.add(this.selectableAvailableClass)\n }\n\n toggleSelectable() {\n this.selectableValue = !this.selectableValue\n }\n\n updateSelectedIds() {\n this.updateActions()\n this.updateSelectAllCheckbox()\n }\n\n updateActions() {\n this.actionTargets.forEach(actionTarget => {\n actionTarget.dispatchEvent(new CustomEvent('update-ids', { detail: {\n ids: this.selectedIds,\n all: this.allSelected\n }}))\n })\n }\n\n selectAllOrNone(event) {\n if (this.allSelected) {\n this.selectNone()\n } else {\n this.selectAll()\n }\n this.updateSelectAllCheckbox()\n }\n\n selectAll() {\n this.checkboxTargets.forEach(checkbox => {\n checkbox.checked = true\n })\n this.updateActions()\n }\n\n selectNone() {\n this.checkboxTargets.forEach(checkbox => {\n checkbox.checked = false\n })\n this.updateActions()\n }\n\n updateSelectAllCheckbox() {\n let checkbox = this.selectAllCheckboxTarget\n let label = this.selectAllLabelTarget\n\n if (this.allSelected) {\n checkbox.checked = true\n checkbox.indeterminate = false\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: true }} ))\n } else if (this.selectedIds.length > 0) {\n checkbox.indeterminate = true\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: false }} ))\n } else {\n checkbox.checked = false\n checkbox.indeterminate = false\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: false }} ))\n }\n }\n\n selectableValueChanged() {\n this.element.classList.toggle(this.selectableClass, this.selectableValue)\n this.updateToggleLabel()\n }\n\n updateToggleLabel() {\n this.selectableToggleTarget.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: this.selectableValue }} ))\n }\n\n get selectedIds() {\n let ids = []\n this.checkboxTargets.forEach(checkbox => {\n if (checkbox.checked) {\n ids.push(checkbox.value)\n }\n })\n return ids\n }\n\n get allSelected() {\n return this.selectedIds.length === this.checkboxTargets.length\n }\n}","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = ['source', 'input', 'button']\n\n copy() {\n this.inputTarget.value = this.sourceTarget.innerText\n this.inputTarget.select()\n document.execCommand('copy')\n this.buttonTarget.innerHTML = '<i id=\"copied\" class=\"fas fa-check w-4 h-4 block text-green-600\"></i>'\n setTimeout(function () {\n document.getElementById('copied').innerHTML = '<i class=\"far fa-copy w-4 h-4 block text-gray-600\"></i>'\n }, 1500)\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\n// TODO Some of this feels really specific to the conversation messages form. Should we rename this controller?\nexport default class extends Controller {\n static targets = ['trixField', 'scroll']\n\n resetOnSuccess(e){\n if(e.detail.success) {\n e.target.reset();\n }\n }\n\n stripTrix(){\n this.trixFieldTargets.forEach(element => {\n this.removeTrailingNewlines(element.editor)\n this.removeTrailingWhitespace(element.editor)\n // When doing this as part of the form submission, Trix does not update the input element's value attribute fast enough.\n // In order to submit the stripped value, we manually update it here to fix the race condition\n element.parentElement.querySelector(\"input\").value = element.innerHTML\n })\n }\n\n submitOnReturn(e) {\n if((e.metaKey || e.ctrlKey) && e.keyCode == 13) {\n e.preventDefault();\n let form = e.target.closest(\"form\")\n this.submitForm(form)\n }\n }\n\n removeTrailingNewlines = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/<br><\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -10) + \"</div>\"\n this.removeTrailingNewlines(trixEditor)\n }\n }\n\n removeTrailingWhitespace = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/ <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -12) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n } else if (trixEditor.element.innerHTML.match(/ <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -13) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n }\n }\n\n submitForm(form) {\n // Right now, Safari and IE don't support the requestSubmit method which is required for Turbo\n // Doing form.submit() doesn't actually fire the submit event which Turbo needs\n if (form.requestSubmit) {\n form.requestSubmit()\n } else {\n form.querySelector(\"[type=submit]\").click()\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = [ \"wrapper\"]\n static classes = [ \"hidden\" ] // necessary because stimulus-reveal will mess with the [hidden] attribute on the wrapper\n static values = {\n showEventName: String,\n hideEventName: String,\n }\n\n toggle() {\n const eventName = this.isWrapperHidden? this.showEventNameValue: this.hideEventNameValue\n if (this.isWrapperHidden) {\n this.showWrapper()\n }\n \n this.wrapperTarget.dispatchEvent(new CustomEvent(eventName))\n }\n \n get isWrapperHidden() {\n return this.wrapperTarget.classList.contains(this.hiddenClass)\n }\n \n showWrapper() {\n this.wrapperTarget.classList.remove(this.hiddenClass)\n }\n \n hideWrapper() {\n this.wrapperTarget.classList.add(this.hiddenClass)\n }\n}","import { identifierForContextKey } from \"@hotwired/stimulus-webpack-helpers\"\n\nimport BulkActionFormController from './bulk_action_form_controller'\nimport BulkActionsController from './bulk_actions_controller'\nimport ClipboardController from './clipboard_controller'\nimport FormController from './form_controller'\nimport MobileMenuController from './mobile_menu_controller'\n\nexport const controllerDefinitions = [\n [BulkActionFormController, 'bulk_action_form_controller.js'],\n [BulkActionsController, 'bulk_actions_controller.js'],\n [ClipboardController, 'clipboard_controller.js'],\n [FormController, 'form_controller.js'],\n [MobileMenuController, 'mobile_menu_controller.js'],\n].map(function(d) {\n const key = d[1]\n const controller = d[0]\n return {\n identifier: identifierForContextKey(key),\n controllerConstructor: controller\n }\n})\n","document.addEventListener(\"turbo:load\", () => {\n if (navigator.userAgent.toLocaleLowerCase().includes('electron')) {\n document.body.classList.add('electron')\n }\n})"],"names":["_class","connect","this","updateAvailability","updateFormAndSubmit","event","recreateIdsHiddenFields","createOrUpdateAllField","updateIds","_event$detail","detail","ids","idsValue","allValue","all","updateButtonLabel","element","classList","toggle","hiddenClass","length","label","buttonIfAllValue","buttonIfIdsValue","replace","buttonTarget","tagName","value","textContent","removeIdsHiddenFields","createIdsHiddenFields","idsHiddenFieldTargets","forEach","field","removeChild","id","document","createElement","type","name","objectNameValue","idsFieldNameValue","appendChild","hasAllHiddenFieldTarget","allHiddenFieldTarget","createAllField","allFieldNameValue","targets","classes","values","buttonIfAll","String","buttonIfIds","Array","Boolean","objectName","idsFieldName","allFieldName","Controller","add","selectableAvailableClass","toggleSelectable","selectableValue","updateSelectedIds","updateActions","updateSelectAllCheckbox","actionTargets","actionTarget","dispatchEvent","selectedIds","allSelected","selectAllOrNone","selectNone","selectAll","checkboxTargets","checkbox","checked","selectAllCheckboxTarget","selectAllLabelTarget","indeterminate","CustomEvent","useAlternate","selectableValueChanged","selectableClass","updateToggleLabel","selectableToggleTarget","push","selectable","copy","inputTarget","sourceTarget","innerText","select","execCommand","innerHTML","setTimeout","getElementById","removeTrailingNewlines","trixEditor","match","slice","removeTrailingWhitespace","resetOnSuccess","e","success","target","reset","stripTrix","trixFieldTargets","editor","parentElement","querySelector","submitOnReturn","metaKey","ctrlKey","keyCode","preventDefault","form","closest","submitForm","requestSubmit","click","isWrapperHidden","showEventNameValue","hideEventNameValue","showWrapper","wrapperTarget","eventName","contains","remove","hideWrapper","showEventName","hideEventName","controllerDefinitions","BulkActionFormController","BulkActionsController","ClipboardController","FormController","MobileMenuController","map","d","identifier","identifierForContextKey","controllerConstructor","controller","addEventListener","navigator","userAgent","toLocaleLowerCase","includes","body"],"mappings":"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"}
|
1
|
+
{"version":3,"file":"bullet-train.js","sources":["../../../node_modules/@hotwired/stimulus-webpack-helpers/dist/stimulus-webpack-helpers.js","../../javascript/controllers/bulk_action_form_controller.js","../../javascript/controllers/bulk_actions_controller.js","../../javascript/controllers/clipboard_controller.js","../../javascript/controllers/form_controller.js","../../javascript/controllers/mobile_menu_controller.js","../../javascript/controllers/index.js","../../javascript/electron/index.js"],"sourcesContent":["/*\nStimulus Webpack Helpers 1.0.0\nCopyright © 2021 Basecamp, LLC\n */\nfunction definitionsFromContext(context) {\n return context.keys()\n .map((key) => definitionForModuleWithContextAndKey(context, key))\n .filter((value) => value);\n}\nfunction definitionForModuleWithContextAndKey(context, key) {\n const identifier = identifierForContextKey(key);\n if (identifier) {\n return definitionForModuleAndIdentifier(context(key), identifier);\n }\n}\nfunction definitionForModuleAndIdentifier(module, identifier) {\n const controllerConstructor = module.default;\n if (typeof controllerConstructor == \"function\") {\n return { identifier, controllerConstructor };\n }\n}\nfunction identifierForContextKey(key) {\n const logicalName = (key.match(/^(?:\\.\\/)?(.+)(?:[_-]controller\\..+?)$/) || [])[1];\n if (logicalName) {\n return logicalName.replace(/_/g, \"-\").replace(/\\//g, \"--\");\n }\n}\n\nexport { definitionForModuleAndIdentifier, definitionForModuleWithContextAndKey, definitionsFromContext, identifierForContextKey };\n","import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n static targets = [ \"button\", \"idsHiddenField\", \"allHiddenField\" ]\n static classes = [ \"hidden\" ]\n static values = {\n buttonIfAll: String,\n buttonIfIds: String,\n ids: Array,\n all: Boolean,\n objectName: String,\n idsFieldName: String,\n allFieldName: String\n }\n\n connect() {\n this.updateAvailability()\n }\n\n updateFormAndSubmit(event) {\n this.recreateIdsHiddenFields()\n this.createOrUpdateAllField()\n return true\n }\n\n updateIds(event) {\n if (event?.detail?.ids) {\n this.idsValue = event.detail.ids\n this.allValue = event.detail.all\n }\n\n this.updateAvailability()\n this.updateButtonLabel()\n }\n\n updateAvailability() {\n this.element.classList.toggle(this.hiddenClass, this.idsValue.length === 0)\n }\n\n updateButtonLabel() {\n let label = this.buttonIfAllValue\n if (this.idsValue.length && this.allValue === false) {\n label = this.buttonIfIdsValue.replace('{num}', this.idsValue.length)\n }\n\n switch (this.buttonTarget.tagName) {\n case 'INPUT': this.buttonTarget.value = label; break;\n default: this.buttonTarget.textContent = label; break;\n }\n }\n\n recreateIdsHiddenFields() {\n this.removeIdsHiddenFields()\n this.createIdsHiddenFields()\n }\n\n removeIdsHiddenFields() {\n this.idsHiddenFieldTargets.forEach(field => {\n this.element.removeChild(field)\n })\n }\n\n createIdsHiddenFields() {\n this.idsValue.forEach(id => {\n let field = document.createElement('input')\n field.type = 'hidden'\n field.name = `${this.objectNameValue}[${this.idsFieldNameValue}][]`\n field.value = id\n this.element.appendChild(field)\n })\n }\n\n createOrUpdateAllField() {\n if (this.hasAllHiddenFieldTarget) {\n this.allHiddenFieldTarget.value = this.allValue? 'true': 'false'\n } else {\n this.createAllField()\n }\n }\n\n createAllField() {\n let field = document.createElement('input')\n field.type = 'hidden'\n field.name = `${this.objectNameValue}[${this.allFieldNameValue}]`\n field.value = this.allValue? 'true': 'false'\n this.element.appendChild(field)\n }\n}","import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n static targets = [ \"checkbox\", \"selectAllCheckbox\", \"action\", \"selectableToggle\", \"selectAllLabel\" ]\n static classes = [ \"selectableAvailable\", \"selectable\" ]\n static values = {\n selectable: Boolean\n }\n\n connect() {\n this.element.classList.add(this.selectableAvailableClass)\n }\n\n toggleSelectable() {\n this.selectableValue = !this.selectableValue\n }\n\n updateSelectedIds() {\n this.updateActions()\n this.updateSelectAllCheckbox()\n }\n\n updateActions() {\n this.actionTargets.forEach(actionTarget => {\n actionTarget.dispatchEvent(new CustomEvent('update-ids', { detail: {\n ids: this.selectedIds,\n all: this.allSelected\n }}))\n })\n }\n\n selectAllOrNone(event) {\n if (this.allSelected) {\n this.selectNone()\n } else {\n this.selectAll()\n }\n this.updateSelectAllCheckbox()\n }\n\n selectAll() {\n this.checkboxTargets.forEach(checkbox => {\n checkbox.checked = true\n })\n this.updateActions()\n }\n\n selectNone() {\n this.checkboxTargets.forEach(checkbox => {\n checkbox.checked = false\n })\n this.updateActions()\n }\n\n updateSelectAllCheckbox() {\n let checkbox = this.selectAllCheckboxTarget\n let label = this.selectAllLabelTarget\n\n if (this.allSelected) {\n checkbox.checked = true\n checkbox.indeterminate = false\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: true }} ))\n } else if (this.selectedIds.length > 0) {\n checkbox.indeterminate = true\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: false }} ))\n } else {\n checkbox.checked = false\n checkbox.indeterminate = false\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: false }} ))\n }\n }\n\n selectableValueChanged() {\n this.element.classList.toggle(this.selectableClass, this.selectableValue)\n this.updateToggleLabel()\n }\n\n updateToggleLabel() {\n this.selectableToggleTarget.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: this.selectableValue }} ))\n }\n\n get selectedIds() {\n let ids = []\n this.checkboxTargets.forEach(checkbox => {\n if (checkbox.checked) {\n ids.push(checkbox.value)\n }\n })\n return ids\n }\n\n get allSelected() {\n return this.selectedIds.length === this.checkboxTargets.length\n }\n}","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = ['source', 'input', 'button']\n\n copy() {\n this.inputTarget.value = this.sourceTarget.innerText\n this.inputTarget.select()\n document.execCommand('copy')\n this.buttonTarget.innerHTML = '<i id=\"copied\" class=\"fas fa-check w-4 h-4 block text-green-600\"></i>'\n setTimeout(function () {\n document.getElementById('copied').innerHTML = '<i class=\"far fa-copy w-4 h-4 block text-gray-600\"></i>'\n }, 1500)\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\n// TODO Some of this feels really specific to the conversation messages form. Should we rename this controller?\nexport default class extends Controller {\n static targets = ['trixField', 'scroll']\n\n resetOnSuccess(e){\n if(e.detail.success) {\n e.target.reset();\n }\n }\n\n stripTrix(){\n this.trixFieldTargets.forEach(element => {\n this.removeTrailingNewlines(element.editor)\n this.removeTrailingWhitespace(element.editor)\n // When doing this as part of the form submission, Trix does not update the input element's value attribute fast enough.\n // In order to submit the stripped value, we manually update it here to fix the race condition\n element.parentElement.querySelector(\"input\").value = element.innerHTML\n })\n }\n\n submitOnReturn(e) {\n if((e.metaKey || e.ctrlKey) && e.keyCode == 13) {\n e.preventDefault();\n let form = e.target.closest(\"form\")\n this.submitForm(form)\n }\n }\n\n removeTrailingNewlines = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/<br><\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -10) + \"</div>\"\n this.removeTrailingNewlines(trixEditor)\n }\n }\n\n removeTrailingWhitespace = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/ <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -12) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n } else if (trixEditor.element.innerHTML.match(/ <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -13) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n }\n }\n\n submitForm(form) {\n // Right now, Safari and IE don't support the requestSubmit method which is required for Turbo\n // Doing form.submit() doesn't actually fire the submit event which Turbo needs\n if (form.requestSubmit) {\n form.requestSubmit()\n } else {\n form.querySelector(\"[type=submit]\").click()\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = [ \"wrapper\"]\n static classes = [ \"hidden\" ] // necessary because stimulus-reveal will mess with the [hidden] attribute on the wrapper\n static values = {\n showEventName: String,\n hideEventName: String,\n }\n\n toggle() {\n const eventName = this.isWrapperHidden? this.showEventNameValue: this.hideEventNameValue\n if (this.isWrapperHidden) {\n this.showWrapper()\n }\n \n this.wrapperTarget.dispatchEvent(new CustomEvent(eventName))\n }\n \n get isWrapperHidden() {\n return this.wrapperTarget.classList.contains(this.hiddenClass)\n }\n \n showWrapper() {\n this.wrapperTarget.classList.remove(this.hiddenClass)\n }\n \n hideWrapper() {\n this.wrapperTarget.classList.add(this.hiddenClass)\n }\n}","import { identifierForContextKey } from \"@hotwired/stimulus-webpack-helpers\"\n\nimport BulkActionFormController from './bulk_action_form_controller'\nimport BulkActionsController from './bulk_actions_controller'\nimport ClipboardController from './clipboard_controller'\nimport FormController from './form_controller'\nimport MobileMenuController from './mobile_menu_controller'\n\nexport const controllerDefinitions = [\n [BulkActionFormController, 'bulk_action_form_controller.js'],\n [BulkActionsController, 'bulk_actions_controller.js'],\n [ClipboardController, 'clipboard_controller.js'],\n [FormController, 'form_controller.js'],\n [MobileMenuController, 'mobile_menu_controller.js'],\n].map(function(d) {\n const key = d[1]\n const controller = d[0]\n return {\n identifier: identifierForContextKey(key),\n controllerConstructor: controller\n }\n})\n","document.addEventListener(\"turbo:load\", () => {\n if (navigator.userAgent.toLocaleLowerCase().includes('electron')) {\n document.body.classList.add('electron')\n }\n})"],"names":["identifierForContextKey","key","logicalName","match","replace","Controller","connect","this","updateAvailability","updateFormAndSubmit","event","recreateIdsHiddenFields","createOrUpdateAllField","updateIds","detail","_event$detail","ids","idsValue","allValue","all","updateButtonLabel","element","classList","toggle","hiddenClass","length","label","buttonIfAllValue","buttonIfIdsValue","buttonTarget","tagName","value","textContent","removeIdsHiddenFields","createIdsHiddenFields","idsHiddenFieldTargets","forEach","field","removeChild","id","document","createElement","type","name","objectNameValue","idsFieldNameValue","appendChild","hasAllHiddenFieldTarget","allHiddenFieldTarget","createAllField","allFieldNameValue","targets","classes","values","buttonIfAll","String","buttonIfIds","Array","Boolean","objectName","idsFieldName","allFieldName","add","selectableAvailableClass","toggleSelectable","selectableValue","updateSelectedIds","updateActions","updateSelectAllCheckbox","actionTargets","actionTarget","dispatchEvent","CustomEvent","selectedIds","allSelected","selectAllOrNone","selectNone","selectAll","checkboxTargets","checkbox","checked","selectAllCheckboxTarget","selectAllLabelTarget","indeterminate","useAlternate","selectableValueChanged","selectableClass","updateToggleLabel","selectableToggleTarget","push","selectable","copy","inputTarget","sourceTarget","innerText","select","execCommand","innerHTML","setTimeout","getElementById","removeTrailingNewlines","trixEditor","slice","removeTrailingWhitespace","resetOnSuccess","e","success","target","reset","stripTrix","trixFieldTargets","editor","parentElement","querySelector","submitOnReturn","metaKey","ctrlKey","keyCode","preventDefault","form","closest","submitForm","requestSubmit","click","eventName","isWrapperHidden","showEventNameValue","hideEventNameValue","showWrapper","wrapperTarget","contains","remove","hideWrapper","showEventName","hideEventName","controllerDefinitions","BulkActionFormController","BulkActionsController","ClipboardController","FormController","MobileMenuController","map","d","controller","identifier","controllerConstructor","addEventListener","navigator","userAgent","toLocaleLowerCase","includes","body"],"mappings":"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"}
|
@@ -42,9 +42,13 @@ module Account::Invitations::ControllerBase
|
|
42
42
|
# POST /invitations/1/accept
|
43
43
|
# POST /invitations/1/accept.json
|
44
44
|
def accept
|
45
|
+
# The user should be alerted when there is no
|
46
|
+
# invitation regardless if they're registered or not.
|
47
|
+
@invitation = Invitation.find_by(uuid: params[:id])
|
48
|
+
flash[:alert] = t("invitations.notifications.doesnt_exist") if @invitation.nil?
|
49
|
+
|
45
50
|
# unless the user is signed in.
|
46
51
|
if !current_user.present?
|
47
|
-
|
48
52
|
# keep track of the uuid of the invitation so we can reload it
|
49
53
|
# after they sign up. at this point we don't even know if it's
|
50
54
|
# valid, but that's fine.
|
@@ -56,22 +60,16 @@ module Account::Invitations::ControllerBase
|
|
56
60
|
# assume the user needs to create an account.
|
57
61
|
# this is not the default for devise, but a sensible default here.
|
58
62
|
redirect_to new_user_registration_path
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
if @invitation
|
65
|
-
@team = @invitation.team
|
66
|
-
if @invitation.is_for?(current_user) || request.post?
|
67
|
-
@invitation.accept_for(current_user)
|
68
|
-
redirect_to account_dashboard_path, notice: I18n.t("invitations.notifications.welcome", team_name: @team.name)
|
69
|
-
else
|
70
|
-
redirect_to account_invitation_path(@invitation.uuid)
|
71
|
-
end
|
63
|
+
elsif @invitation
|
64
|
+
@team = @invitation.team
|
65
|
+
if @invitation.is_for?(current_user) || request.post?
|
66
|
+
@invitation.accept_for(current_user)
|
67
|
+
redirect_to account_dashboard_path, notice: I18n.t("invitations.notifications.welcome", team_name: @team.name)
|
72
68
|
else
|
73
|
-
redirect_to
|
69
|
+
redirect_to account_invitation_path(@invitation.uuid)
|
74
70
|
end
|
71
|
+
else
|
72
|
+
redirect_to account_dashboard_path
|
75
73
|
|
76
74
|
end
|
77
75
|
end
|
@@ -1,3 +1,14 @@
|
|
1
1
|
class SessionsController < Devise::SessionsController
|
2
2
|
include Sessions::ControllerBase
|
3
|
+
|
4
|
+
def destroy
|
5
|
+
if params.include?(:onboard_logout)
|
6
|
+
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
|
7
|
+
set_flash_message! :notice, :signed_out if signed_out
|
8
|
+
yield if block_given?
|
9
|
+
redirect_to root_path
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
3
14
|
end
|
@@ -62,7 +62,7 @@ module Memberships::Base
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def tombstone?
|
65
|
-
user.nil? && invitation.nil?
|
65
|
+
user.nil? && invitation.nil? && !platform_agent?
|
66
66
|
end
|
67
67
|
|
68
68
|
def last_admin?
|
@@ -138,4 +138,8 @@ module Memberships::Base
|
|
138
138
|
def should_receive_notifications?
|
139
139
|
invitation.present? || user.present?
|
140
140
|
end
|
141
|
+
|
142
|
+
def platform_agent?
|
143
|
+
platform_agent_of_id.present?
|
144
|
+
end
|
141
145
|
end
|
@@ -66,8 +66,7 @@ module Users::Base
|
|
66
66
|
|
67
67
|
def create_default_team
|
68
68
|
# This creates a `Membership`, because `User` `has_many :teams, through: :memberships`
|
69
|
-
|
70
|
-
default_team = teams.create(name: "Your Team", time_zone: time_zone)
|
69
|
+
default_team = teams.create(name: I18n.t("teams.new.default_team_name"), time_zone: time_zone)
|
71
70
|
memberships.find_by(team: default_team).update role_ids: [Role.admin.id]
|
72
71
|
update(current_team: default_team)
|
73
72
|
end
|
@@ -46,7 +46,9 @@
|
|
46
46
|
<% else %>
|
47
47
|
<%= link_to t('.buttons.promote'), [:promote, :account, @membership], method: :post, data: { confirm: t('global.confirm_message') }, class: first_button_primary if can? :promote, @membership %>
|
48
48
|
<% end %>
|
49
|
-
|
49
|
+
<% if (can? :destroy, @membership) && (!@membership.platform_agent?) %>
|
50
|
+
<%= button_to t(".buttons.#{membership_destroy_locale_key(@membership)}"), [:account, @membership], method: :delete, data: { confirm: t(".buttons.confirmations.#{membership_destroy_locale_key(@membership)}", model_locales(@membership)) }, class: first_button_primary %>
|
51
|
+
<% end %>
|
50
52
|
<% end %>
|
51
53
|
|
52
54
|
<%= link_to t('global.buttons.back'), [:account, @team, :memberships], class: first_button_primary %>
|
@@ -38,7 +38,7 @@
|
|
38
38
|
<% if other_teams.any? %>
|
39
39
|
<%= link_to t('global.buttons.back'), main_app.account_teams_path, class: first_button_primary %>
|
40
40
|
<% else %>
|
41
|
-
<%= link_to t('menus.main.labels.logout'), main_app.destroy_user_session_path, class: first_button_primary, method: 'delete' %>
|
41
|
+
<%= link_to t('menus.main.labels.logout'), main_app.destroy_user_session_path(@user, onboard_logout: true), class: first_button_primary, method: 'delete' %>
|
42
42
|
<% end %>
|
43
43
|
</div>
|
44
44
|
<% end %>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
en:
|
2
|
+
framework_packages:
|
3
|
+
bullet_train:
|
4
|
+
git: "bullet-train-co/bullet_train-base"
|
5
|
+
npm: "@bullet-train/bullet-train"
|
6
|
+
bullet_train-api:
|
7
|
+
git: "bullet-train-co/bullet_train-api"
|
8
|
+
bullet_train-fields:
|
9
|
+
git: "bullet-train-co/bullet_train-fields"
|
10
|
+
npm: "@bullet-train/fields"
|
11
|
+
bullet_train-has_uuid:
|
12
|
+
git: "bullet-train-co/bullet_train-has_uuid"
|
13
|
+
bullet_train-incoming_webhooks:
|
14
|
+
git: "bullet-train-co/bullet_train-incoming_webhooks"
|
15
|
+
bullet_train-integrations:
|
16
|
+
git: "bullet-train-co/bullet_train-integrations"
|
17
|
+
bullet_train-integrations-stripe:
|
18
|
+
git: "bullet-train-co/bullet_train-base-integrations-stripe"
|
19
|
+
bullet_train-obfuscates_id:
|
20
|
+
git: "bullet-train-co/bullet_train-obfuscates_id"
|
21
|
+
bullet_train-outgoing_webhooks:
|
22
|
+
git: "bullet-train-co/bullet_train-outgoing_webhooks"
|
23
|
+
bullet_train-outgoing_webhooks-core:
|
24
|
+
git: "bullet-train-co/bullet_train-outgoing_webhooks-core"
|
25
|
+
bullet_train-scope_questions:
|
26
|
+
git: "bullet-train-co/bullet_train-scope_questions"
|
27
|
+
bullet_train-scope_validator:
|
28
|
+
git: "bullet-train-co/bullet_train-scope_validator"
|
29
|
+
bullet_train-sortable:
|
30
|
+
git: "bullet-train-co/bullet_train-sortable"
|
31
|
+
npm: "@bullet-train/bullet-train-sortable"
|
32
|
+
bullet_train-super_scaffolding:
|
33
|
+
git: "bullet-train-co/bullet_train-super_scaffolding"
|
34
|
+
bullet_train-super_load_and_authorize_resource:
|
35
|
+
git: "bullet-train-co/bullet_train-super_load_and_authorize_resource"
|
36
|
+
bullet_train-themes:
|
37
|
+
git: "bullet-train-co/bullet_train-themes"
|
38
|
+
bullet_train-themes-base:
|
39
|
+
git: "bullet-train-co/bullet_train-themes-base"
|
40
|
+
bullet_train-themes-light:
|
41
|
+
git: "bullet-train-co/bullet_train-themes-light"
|
42
|
+
bullet_train-themes-tailwind_css:
|
43
|
+
git: "bullet-train-co/bullet_train-themes-tailwind_css"
|
@@ -56,6 +56,7 @@ en:
|
|
56
56
|
created: 'Invitation was successfully created.'
|
57
57
|
updated: 'Invitation was successfully updated.'
|
58
58
|
destroyed: 'Invitation was successfully destroyed.'
|
59
|
+
doesnt_exist: Sorry, but we couldn't find your invitation. Please contact an administrator on the team to have them send you another one.
|
59
60
|
values:
|
60
61
|
name: 'Invitation to Join %{team_name}'
|
61
62
|
account:
|
data/docs/action-models.md
CHANGED
@@ -44,7 +44,7 @@ end
|
|
44
44
|
|
45
45
|
### 1. Purchase Bullet Train Pro
|
46
46
|
|
47
|
-
First, [purchase Bullet Train Pro](
|
47
|
+
First, [purchase Bullet Train Pro](https://buy.stripe.com/aEU7vc4dBfHtfO89AV). Once you've completed this process, you'll be issued a private token for the Bullet Train Pro package server. The process is currently completed manually, so you may have to way a little to receive your keys.
|
48
48
|
|
49
49
|
### 2. Install the Package
|
50
50
|
|
@@ -56,7 +56,7 @@ source "https://YOUR_TOKEN_HERE@gem.fury.io/bullettrain" do
|
|
56
56
|
end
|
57
57
|
```
|
58
58
|
|
59
|
-
Don't forget to run `bundle install` and `rails restart`.
|
59
|
+
Don't forget to run `bundle install` and `rails restart`.
|
60
60
|
|
61
61
|
## Super Scaffolding Commands
|
62
62
|
|
@@ -93,7 +93,7 @@ def perform_on_target(project)
|
|
93
93
|
end
|
94
94
|
```
|
95
95
|
|
96
|
-
You're done!
|
96
|
+
You're done!
|
97
97
|
|
98
98
|
## Additional Examples
|
99
99
|
|
@@ -133,7 +133,7 @@ Action Models that _can_ target many objects are by far the most common, but it'
|
|
133
133
|
|
134
134
|
### Targets One
|
135
135
|
|
136
|
-
Sometimes you have an action that will only ever target one object at a time. In this case, they're generated a little differently and live under (and belong to) the model they target.
|
136
|
+
Sometimes you have an action that will only ever target one object at a time. In this case, they're generated a little differently and live under (and belong to) the model they target.
|
137
137
|
|
138
138
|
When deciding between "targets many" and "targets one", our recommendation is that you only use "targets one" for actions that you know for certain could never make sense targeting more than one object at the same time. For example, if you're creating a send action for an email that includes configuration options and scheduling details, you may be reasonably confident that you never need users to be able to schedule two different emails at the same time with the same settings. That would be a good candidate for a "targets one" action.
|
139
139
|
|
@@ -56,3 +56,14 @@ Here is the same example, with search enabled:
|
|
56
56
|
choices: @project.valid_memberships.map { |membership| [membership.name, membership.id] },
|
57
57
|
html_options: {multiple: true}, other_options: {search: true} %>
|
58
58
|
</code></pre>
|
59
|
+
|
60
|
+
## Accepting New Entries
|
61
|
+
|
62
|
+
Here is an example allowing a new option to be entered by the user:
|
63
|
+
|
64
|
+
<pre><code><%= render 'shared/fields/super_select', form: form, method: :delay_minutes,
|
65
|
+
choices: %w(1 5 10 30).map { |value| [value, value] },
|
66
|
+
other_options: {accepts_new: true} %>
|
67
|
+
</code></pre>
|
68
|
+
|
69
|
+
Note: this will set the option `value` (which will be submitted to the server) to the entered text.
|
data/docs/indirection.md
CHANGED
@@ -38,7 +38,32 @@ If you're looking at a rendered view in the browser, it can be hard to know whic
|
|
38
38
|
<!-- BEGIN /Users/andrewculver/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.10/app/views/themes/light/workflow/_box.html.erb -->
|
39
39
|
```
|
40
40
|
|
41
|
-
|
41
|
+
If you want to customize files like this that you find annotated in your browser, you can use the `--interactive` flag to eject the file to your main application, or simply open it in your code editor.
|
42
|
+
|
43
|
+
```
|
44
|
+
> bin/resolve --interactive
|
45
|
+
|
46
|
+
OK, paste what you've got for us and hit <Return>!
|
47
|
+
|
48
|
+
<!-- BEGIN /Users/andrewculver/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.10/app/views/themes/light/workflow/_box.html.erb -->
|
49
|
+
|
50
|
+
Absolute path:
|
51
|
+
/Users/andrewculver/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.10/app/views/themes/light/workflow/_box.html.erb
|
52
|
+
|
53
|
+
Package name:
|
54
|
+
bullet_train-themes-light-1.0.10
|
55
|
+
|
56
|
+
|
57
|
+
Would you like to eject the file into the local project? (y/n)
|
58
|
+
n
|
59
|
+
|
60
|
+
Would you like to open `/Users/andrewculver/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.10/app/views/themes/light/workflow/_box.html.erb`? (y/n)
|
61
|
+
y
|
62
|
+
```
|
63
|
+
|
64
|
+
You may also want to consider using `bin/develop`, which will clone the Bullet Train package of your choice to `local/` within your main application's root directory. Running this command will also automatically link the package to your main application and open it in the code editor for you, so you can start using the cloned repository and make changes to your main application right away.
|
65
|
+
|
66
|
+
Note that in the example above, the view in question isn't actually coming from the application repository. Instead, it's being included from the `bullet_train-themes-light` package. For further instructions on how to customize it, see [Overriding Framework Defaults](/docs/overriding.md).
|
42
67
|
|
43
68
|
### Drilling Down on Translation Keys
|
44
69
|
|
data/docs/onboarding.md
CHANGED
@@ -13,29 +13,29 @@ The "user email" onboarding step is specifically used in situations where a user
|
|
13
13
|
|
14
14
|
## Relevant Files
|
15
15
|
|
16
|
-
|
16
|
+
Each of the following files exist within their own respective Bullet Train packages. Make sure you navigate to the correct package when searching for the following files.
|
17
17
|
|
18
18
|
### Controllers
|
19
|
-
- `ensure_onboarding_is_complete` in `app/controllers/account/application_controller.rb`
|
20
|
-
- `app/controllers/account/onboarding/user_details_controller.rb`
|
21
|
-
- `app/controllers/account/onboarding/user_email_controller.rb`
|
19
|
+
- `ensure_onboarding_is_complete` in `bullet_train/app/controllers/account/application_controller.rb`
|
20
|
+
- `bullet_train-base/app/controllers/account/onboarding/user_details_controller.rb`
|
21
|
+
- `bullet_train-base/app/controllers/account/onboarding/user_email_controller.rb`
|
22
22
|
|
23
23
|
### Views
|
24
|
-
- `app/views/account/onboarding/user_details/edit.html.erb`
|
25
|
-
- `app/views/account/onboarding/user_email/edit.html.erb`
|
24
|
+
- `bullet_train-base/app/views/account/onboarding/user_details/edit.html.erb`
|
25
|
+
- `bullet_train-base/app/views/account/onboarding/user_email/edit.html.erb`
|
26
26
|
|
27
27
|
### Models
|
28
|
-
- `user#details_provided?` in `app/models/
|
28
|
+
- `user#details_provided?` in `bullet_train-base/app/models/concerns/users/base.rb`
|
29
29
|
|
30
30
|
### Routes
|
31
|
-
- `namespace :onboarding` in `config/routes.rb`
|
31
|
+
- `namespace :onboarding` in `bullet_train-base/config/routes.rb` and `bullet_train/config/routes.rb`
|
32
32
|
|
33
33
|
## Adding Additional Steps
|
34
34
|
Although you can implement onboarding steps from scratch, we always just copy and paste one of the existing steps as a starting point, like so:
|
35
35
|
|
36
36
|
1. Copy, rename, and modify of the existing onboarding controllers.
|
37
37
|
2. Copy, rename, and modify the corresponding `edit.html.erb` view.
|
38
|
-
3. Copy and rename the route entry in `config/routes.rb`.
|
39
|
-
4. Add the appropriate gating logic in `ensure_onboarding_is_complete` in `app/controllers/account/application_controller.rb`
|
38
|
+
3. Copy and rename the route entry in `bullet_train-base/config/routes.rb`.
|
39
|
+
4. Add the appropriate gating logic in `ensure_onboarding_is_complete` in `bullet_train/app/controllers/account/application_controller.rb`
|
40
40
|
|
41
41
|
Onboarding steps aren't limited to targeting `User` models. It's possible to add onboarding steps to help flesh out team `Membership` records or `Team` records as well. You can use this pattern for setting up any sort of required data for either the user or the team.
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# Super Scaffolding with the `--sortable` option
|
2
|
+
|
3
|
+
When issuing a `bin/super-scaffold crud` command, you can pass the `--sortable` option like this:
|
4
|
+
|
5
|
+
```
|
6
|
+
# E.g. Pages belong to a Site and are sortable via drag-and-drop:
|
7
|
+
rails g model Page site:references name:string path:text
|
8
|
+
bin/super-scaffold crud Page Site,Team name:text path:text --sortable
|
9
|
+
```
|
10
|
+
|
11
|
+
The `--sortable` option:
|
12
|
+
|
13
|
+
1. Wraps the table's body in a `sortable` Stimulus controller, providing drag-and-drop re-ordering;
|
14
|
+
2. Adds a `reorder` action to your resource via `include SortableActions`, triggered automatically on re-order;
|
15
|
+
3. Adds a migration to add the `sort_order` column to your model to store the ordering;
|
16
|
+
4. Adds a `default_scope` which orders by `sort_order` and auto increments `sort_order` on create via `include Sortable` on the model.
|
17
|
+
|
18
|
+
## Disabling Saving on Re-order
|
19
|
+
|
20
|
+
By default, a call to save the new `sort_order` is triggered automatically on re-order.
|
21
|
+
|
22
|
+
### To disable auto-saving
|
23
|
+
|
24
|
+
Add the `data-sortable-save-on-reorder-value="false"` param on the `sortable` root element:
|
25
|
+
|
26
|
+
```html
|
27
|
+
<tbody data-controller="sortable"
|
28
|
+
data-sortable-save-on-reorder-value="false"
|
29
|
+
...
|
30
|
+
>
|
31
|
+
```
|
32
|
+
|
33
|
+
### To manually fire the save action via a button
|
34
|
+
|
35
|
+
Since the button won't be part of the `sortable` root element's descendants (all its direct descendants are sortable by default), you'll need to wrap both the `sortable` element and the save button in a new Stimulus controlled ancestor element.
|
36
|
+
|
37
|
+
```js
|
38
|
+
/* sortable-wrapper_controller.js */
|
39
|
+
import { Controller } from "@hotwired/stimulus"
|
40
|
+
|
41
|
+
export default class extends Controller {
|
42
|
+
static targets = [ "sortable" ]
|
43
|
+
|
44
|
+
saveSortOrder() {
|
45
|
+
if (!this.hasSortableTarget) { return }
|
46
|
+
this.sortableTarget.dispatchEvent(new CustomEvent("save-sort-order"))
|
47
|
+
}
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
51
|
+
On the button, add a `data-action`
|
52
|
+
|
53
|
+
```html
|
54
|
+
<button data-action="sortable-wrapper#saveSortOrder">Save Sort Order</button>
|
55
|
+
```
|
56
|
+
|
57
|
+
And on the `sortable` element, catch the `save-sort-order` event and define it as the `sortable` target for the `sortable-wrapper` controller:
|
58
|
+
|
59
|
+
```html
|
60
|
+
<tbody data-controller="sortable"
|
61
|
+
data-sortable-save-on-reorder-value="false"
|
62
|
+
data-action="save-sort-order->sortable#saveSortOrder"
|
63
|
+
data-sortable-wrapper-target="sortable"
|
64
|
+
...
|
65
|
+
>
|
66
|
+
```
|
67
|
+
|
68
|
+
## Events
|
69
|
+
|
70
|
+
Under the hood, the `sortable` Stimulus controller uses the [dragula](https://github.com/bevacqua/dragula) library.
|
71
|
+
|
72
|
+
All of the events that `dragula` defines are re-dispatched as native DOM events. The native DOM event name is prefixed with `sortable:`
|
73
|
+
|
74
|
+
| dragula event name | DOM event name |
|
75
|
+
|---------------------|----------------------|
|
76
|
+
| drag | sortable:drag |
|
77
|
+
| dragend | sortable:dragend |
|
78
|
+
| drop | sortable:drop |
|
79
|
+
| cancel | sortable:cancel |
|
80
|
+
| remove | sortable:remove |
|
81
|
+
| shadow | sortable:shadow |
|
82
|
+
| over | sortable:over |
|
83
|
+
| out | sortable:out |
|
84
|
+
| cloned | sortable:cloned |
|
85
|
+
|
86
|
+
The original event's listener arguments are passed to the native DOM event as a simple numbered Array under `event.detail.args`. See [dragula's list of events](https://github.com/bevacqua/dragula#drakeon-events) for the listener arguments.
|
87
|
+
|
88
|
+
### Example: Asking for Confirmation on the `drop` Event
|
89
|
+
|
90
|
+
Let's say we'd like to ask the user to confirm before saving the new sort order:
|
91
|
+
|
92
|
+
> Are you sure you want to place DROPPED ITEM before SIBLING ITEM?
|
93
|
+
|
94
|
+
```js
|
95
|
+
/* confirm-reorder_controller.js */
|
96
|
+
import { Controller } from "@hotwired/stimulus"
|
97
|
+
|
98
|
+
export default class extends Controller {
|
99
|
+
static targets = [ "sortable" ]
|
100
|
+
|
101
|
+
requestConfirmation(event) {
|
102
|
+
const [el, target, source, sibling] = event.detail?.args
|
103
|
+
|
104
|
+
// sibling will be undefined if dropped in last position, taking a shortcut here
|
105
|
+
const areYouSure = `Are you sure you want to place ${el.dataset.name} before ${sibling.dataset.name}?`
|
106
|
+
|
107
|
+
// let's suppose each <tr> in sortable has a data-name attribute
|
108
|
+
if (confirm(areYouSure)) {
|
109
|
+
this.sortableTarget.dispatchEvent(new CustomEvent('save-sort-order'))
|
110
|
+
} else {
|
111
|
+
this.revertToOriginalOrder()
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
prepareForRevertOnCancel(event) {
|
116
|
+
// we're assuming we can swap out the HTML safely
|
117
|
+
this.originalSortableHTML = this.sortableTarget.innerHTML
|
118
|
+
}
|
119
|
+
|
120
|
+
revertToOriginalOrder() {
|
121
|
+
if (this.originalSortableHTML === undefined) { return }
|
122
|
+
this.sortableTarget.innerHTML = this.originalSortableHTML
|
123
|
+
this.originalSortableHTML = undefined
|
124
|
+
}
|
125
|
+
}
|
126
|
+
```
|
127
|
+
|
128
|
+
And on the `sortable` element, catch the `sortable:drop`, `sortable:drag` (for catching when dragging starts) and `save-sort-order` events. Also define it as the `sortable` target for the `confirm-reorder` controller:
|
129
|
+
|
130
|
+
```html
|
131
|
+
<tbody data-controller="sortable"
|
132
|
+
data-sortable-save-on-reorder-value="false"
|
133
|
+
data-action="sortable:drop->confirm-reorder#requestConfirmation sortable:drag->confirm-reorder#prepareForRevertOnCancel save-sort-order->sortable#saveSortOrder"
|
134
|
+
data-confirm-reorder-target="sortable"
|
135
|
+
...
|
136
|
+
>
|
137
|
+
```
|
data/docs/super-scaffolding.md
CHANGED
@@ -13,17 +13,14 @@ module BulletTrain
|
|
13
13
|
source_file = calculate_source_file_details
|
14
14
|
|
15
15
|
if source_file[:absolute_path]
|
16
|
+
puts ""
|
17
|
+
puts "Absolute path:".green
|
18
|
+
puts " #{source_file[:absolute_path]}".green
|
16
19
|
puts ""
|
17
20
|
if source_file[:package_name].present?
|
18
|
-
puts "Absolute path:".green
|
19
|
-
puts " #{source_file[:absolute_path]}".green
|
20
|
-
puts ""
|
21
21
|
puts "Package name:".green
|
22
22
|
puts " #{source_file[:package_name]}".green
|
23
23
|
else
|
24
|
-
puts "Project path:".green
|
25
|
-
puts " #{source_file[:project_path]}".green
|
26
|
-
puts ""
|
27
24
|
puts "Note: If this file was previously ejected from a package, we can no longer see which package it came from. However, it should say at the top of the file where it was ejected from.".yellow
|
28
25
|
end
|
29
26
|
puts ""
|
@@ -89,37 +86,28 @@ module BulletTrain
|
|
89
86
|
package_name: nil,
|
90
87
|
}
|
91
88
|
|
92
|
-
result[:absolute_path] = class_path || partial_path || locale_path
|
93
|
-
|
89
|
+
result[:absolute_path] = file_path || class_path || partial_path || locale_path
|
94
90
|
if result[:absolute_path]
|
95
|
-
if result[:absolute_path].include?("
|
96
|
-
# The annotated path for devise doesn't actually return an absolute path,
|
97
|
-
# so we have to do some extra steps to get the correct string.
|
98
|
-
relative_partial_path = result[:absolute_path]
|
99
|
-
# If it's a devise partial, it should be coming from bullet_train-base
|
100
|
-
base_path = "#{`bundle show bullet_train`.chomp}/" + relative_partial_path
|
101
|
-
result[:absolute_path] = base_path
|
102
|
-
else
|
91
|
+
if result[:absolute_path].include?("/bullet_train")
|
103
92
|
base_path = "bullet_train" + result[:absolute_path].partition("/bullet_train").last
|
104
|
-
end
|
105
93
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
94
|
+
# Try to calculate which package the file is from, and what it's path is within that project.
|
95
|
+
["app", "config", "lib"].each do |directory|
|
96
|
+
regex = /\/#{directory}\//
|
97
|
+
if base_path.match?(regex)
|
98
|
+
project_path = "./#{directory}/#{base_path.rpartition(regex).last}"
|
99
|
+
package_name = base_path.rpartition(regex).first.split("/").last
|
100
|
+
# If the "package name" is actually just the local project directory.
|
101
|
+
if package_name == `pwd`.chomp.split("/").last
|
102
|
+
package_name = nil
|
103
|
+
end
|
116
104
|
|
117
|
-
|
118
|
-
|
105
|
+
result[:project_path] = project_path
|
106
|
+
result[:package_name] = package_name
|
107
|
+
end
|
119
108
|
end
|
120
109
|
end
|
121
110
|
end
|
122
|
-
|
123
111
|
result
|
124
112
|
end
|
125
113
|
|
@@ -135,9 +123,24 @@ module BulletTrain
|
|
135
123
|
end
|
136
124
|
|
137
125
|
def partial_path
|
138
|
-
|
126
|
+
begin
|
127
|
+
annotated_path = ApplicationController.render(template: "bullet_train/partial_resolver", layout: nil, assigns: {needle: @needle}).lines[1].chomp
|
128
|
+
rescue ActionView::Template::Error => e
|
129
|
+
# This is a really hacky way to get the file name, but the reason we're getting an error in the first place is because
|
130
|
+
# the partial requires locals that we aren't providing in the ApplicationController.render call above,
|
131
|
+
# resulting in an undefined local variable error. We do however get the file name, which we can pass back to the developer.
|
132
|
+
return e.file_name
|
133
|
+
end
|
134
|
+
|
139
135
|
if annotated_path =~ /<!-- BEGIN (\S*) -->/
|
140
|
-
|
136
|
+
# If the developer enters a partial that is in bullet_train-base like devise/shared/oauth or devise/shared/links,
|
137
|
+
# it will return a string starting with app/ so we simply point them to the file in this repository.
|
138
|
+
if annotated_path.match?(/^<!-- BEGIN app/) && !ejected_theme?
|
139
|
+
gem_path = `bundle show bullet_train`.chomp
|
140
|
+
"#{gem_path}/#{$1}"
|
141
|
+
else
|
142
|
+
$1
|
143
|
+
end
|
141
144
|
else
|
142
145
|
raise "It looks like `config.action_view.annotate_rendered_view_with_filenames` isn't enabled?"
|
143
146
|
end
|
@@ -168,5 +171,11 @@ module BulletTrain
|
|
168
171
|
|
169
172
|
nil
|
170
173
|
end
|
174
|
+
|
175
|
+
def ejected_theme?
|
176
|
+
current_theme_symbol = File.read("#{Rails.root}/app/helpers/application_helper.rb").split("\n").find { |str| str.match?(/\s+:.*/) }
|
177
|
+
current_theme = current_theme_symbol.delete(":").strip
|
178
|
+
current_theme != "light" && Dir.exist?("#{Rails.root}/app/assets/stylesheets/#{current_theme}")
|
179
|
+
end
|
171
180
|
end
|
172
181
|
end
|
data/lib/bullet_train/version.rb
CHANGED
data/lib/bullet_train.rb
CHANGED
@@ -18,8 +18,6 @@ require "cancancan"
|
|
18
18
|
require "possessive"
|
19
19
|
require "sidekiq"
|
20
20
|
require "fastimage"
|
21
|
-
require "pry"
|
22
|
-
require "pry-stack_explorer"
|
23
21
|
require "awesome_print"
|
24
22
|
require "microscope"
|
25
23
|
require "http_accept_language"
|
@@ -37,6 +35,8 @@ require "devise/pwned_password"
|
|
37
35
|
module BulletTrain
|
38
36
|
mattr_accessor :routing_concerns, default: []
|
39
37
|
mattr_accessor :linked_gems, default: ["bullet_train"]
|
38
|
+
mattr_accessor :parent_class, default: "Team"
|
39
|
+
mattr_accessor :base_class, default: "ApplicationRecord"
|
40
40
|
end
|
41
41
|
|
42
42
|
def default_url_options_from_base_url
|
@@ -47,6 +47,12 @@ namespace :bullet_train do
|
|
47
47
|
input = $1
|
48
48
|
end
|
49
49
|
|
50
|
+
# Append the main application's path if the file is a local file.
|
51
|
+
# i.e. - app/views/layouts/_head.html.erb
|
52
|
+
if input.match?(/^app/)
|
53
|
+
input = "#{Rails.root}/#{input}"
|
54
|
+
end
|
55
|
+
|
50
56
|
ARGV.unshift input.strip
|
51
57
|
end
|
52
58
|
|
@@ -76,69 +82,7 @@ namespace :bullet_train do
|
|
76
82
|
puts ""
|
77
83
|
end
|
78
84
|
|
79
|
-
|
80
|
-
framework_packages = {
|
81
|
-
"bullet_train" => {
|
82
|
-
git: "bullet-train-co/bullet_train-base",
|
83
|
-
npm: "@bullet-train/bullet-train"
|
84
|
-
},
|
85
|
-
"bullet_train-api" => {
|
86
|
-
git: "bullet-train-co/bullet_train-api",
|
87
|
-
},
|
88
|
-
"bullet_train-fields" => {
|
89
|
-
git: "bullet-train-co/bullet_train-fields",
|
90
|
-
npm: "@bullet-train/fields"
|
91
|
-
},
|
92
|
-
"bullet_train-has_uuid" => {
|
93
|
-
git: "bullet-train-co/bullet_train-has_uuid",
|
94
|
-
},
|
95
|
-
"bullet_train-incoming_webhooks" => {
|
96
|
-
git: "bullet-train-co/bullet_train-incoming_webhooks",
|
97
|
-
},
|
98
|
-
"bullet_train-integrations" => {
|
99
|
-
git: "bullet-train-co/bullet_train-integrations",
|
100
|
-
},
|
101
|
-
"bullet_train-integrations-stripe" => {
|
102
|
-
git: "bullet-train-co/bullet_train-base-integrations-stripe",
|
103
|
-
},
|
104
|
-
"bullet_train-obfuscates_id" => {
|
105
|
-
git: "bullet-train-co/bullet_train-obfuscates_id",
|
106
|
-
},
|
107
|
-
"bullet_train-outgoing_webhooks" => {
|
108
|
-
git: "bullet-train-co/bullet_train-outgoing_webhooks",
|
109
|
-
},
|
110
|
-
"bullet_train-outgoing_webhooks-core" => {
|
111
|
-
git: "bullet-train-co/bullet_train-outgoing_webhooks-core",
|
112
|
-
},
|
113
|
-
"bullet_train-scope_questions" => {
|
114
|
-
git: "bullet-train-co/bullet_train-scope_questions",
|
115
|
-
},
|
116
|
-
"bullet_train-scope_validator" => {
|
117
|
-
git: "bullet-train-co/bullet_train-scope_validator",
|
118
|
-
},
|
119
|
-
"bullet_train-sortable" => {
|
120
|
-
git: "bullet-train-co/bullet_train-sortable",
|
121
|
-
npm: "@bullet-train/bullet-train-sortable"
|
122
|
-
},
|
123
|
-
"bullet_train-super_scaffolding" => {
|
124
|
-
git: "bullet-train-co/bullet_train-super_scaffolding",
|
125
|
-
},
|
126
|
-
"bullet_train-super_load_and_authorize_resource" => {
|
127
|
-
git: "bullet-train-co/bullet_train-super_load_and_authorize_resource",
|
128
|
-
},
|
129
|
-
"bullet_train-themes" => {
|
130
|
-
git: "bullet-train-co/bullet_train-themes",
|
131
|
-
},
|
132
|
-
"bullet_train-themes-base" => {
|
133
|
-
git: "bullet-train-co/bullet_train-themes-base",
|
134
|
-
},
|
135
|
-
"bullet_train-themes-light" => {
|
136
|
-
git: "bullet-train-co/bullet_train-themes-light",
|
137
|
-
},
|
138
|
-
"bullet_train-themes-tailwind_css" => {
|
139
|
-
git: "bullet-train-co/bullet_train-themes-tailwind_css",
|
140
|
-
},
|
141
|
-
}
|
85
|
+
framework_packages = I18n.t("framework_packages")
|
142
86
|
|
143
87
|
puts "Which framework package do you want to work on?".blue
|
144
88
|
puts ""
|
@@ -156,23 +100,46 @@ namespace :bullet_train do
|
|
156
100
|
|
157
101
|
puts "OK! Let's work on `#{gem}` together!".green
|
158
102
|
puts ""
|
159
|
-
puts "First, we're going to clone a copy of the package repository.".blue
|
160
|
-
|
161
|
-
# TODO Prompt whether they want to check out their own forked version of the repository.
|
162
103
|
|
163
104
|
if File.exist?("local/#{gem}")
|
164
|
-
puts "
|
165
|
-
puts "
|
166
|
-
|
167
|
-
|
105
|
+
puts "We found the repository in `local/#{gem}`. We will try to use what's already there.".yellow
|
106
|
+
puts ""
|
107
|
+
|
108
|
+
# Adding these flags enables us to execute git commands in the gem from our starter repo.
|
109
|
+
work_tree_flag = "--work-tree=local/#{gem}"
|
110
|
+
git_dir_flag = "--git-dir=local/#{gem}/.git"
|
168
111
|
|
169
|
-
|
170
|
-
|
112
|
+
git_status = `git #{work_tree_flag} #{git_dir_flag} status`
|
113
|
+
unless git_status.match?("nothing to commit, working tree clean")
|
114
|
+
puts "This package currently has uncommitted changes.".red
|
115
|
+
puts "Please make sure the branch is clean and try again.".red
|
116
|
+
exit
|
117
|
+
end
|
118
|
+
|
119
|
+
current_branch = `git #{work_tree_flag} #{git_dir_flag} branch`.split("\n").select { |branch_name| branch_name.match?(/^\*\s/) }.pop.gsub(/^\*\s/, "")
|
120
|
+
unless current_branch == "main"
|
121
|
+
puts "Previously on #{current_branch}.".blue
|
122
|
+
puts "Switching local/#{gem} to main branch.".blue
|
123
|
+
stream("git #{work_tree_flag} #{git_dir_flag} checkout main")
|
124
|
+
end
|
125
|
+
|
126
|
+
puts "Updating the main branch with the latest changes.".blue
|
127
|
+
stream("git #{work_tree_flag} #{git_dir_flag} pull origin main")
|
171
128
|
else
|
172
|
-
|
129
|
+
# Use https:// URLs when using this task in Gitpod.
|
130
|
+
stream "git clone #{`whoami`.chomp == "gitpod" ? "https://github.com/" : "git@github.com:"}#{details[:git]}.git local/#{gem}"
|
173
131
|
end
|
174
132
|
|
175
|
-
|
133
|
+
stream("git #{work_tree_flag} #{git_dir_flag} fetch")
|
134
|
+
stream("git #{work_tree_flag} #{git_dir_flag} branch -r")
|
135
|
+
puts "The above is a list of remote branches.".blue
|
136
|
+
puts "If there's one you'd like to work on, please enter the branch name and press <Enter>.".blue
|
137
|
+
puts "If not, just press <Enter> to continue.".blue
|
138
|
+
input = $stdin.gets.strip
|
139
|
+
unless input.empty?
|
140
|
+
puts "Switching to #{input.gsub("origin/", "")}".blue # TODO: Should we remove origin/ here if the developer types it?
|
141
|
+
stream("git #{work_tree_flag} #{git_dir_flag} checkout #{input}")
|
142
|
+
end
|
176
143
|
|
177
144
|
puts ""
|
178
145
|
puts "Now we'll try to link up that repository in the `Gemfile`.".blue
|
@@ -210,7 +177,7 @@ namespace :bullet_train do
|
|
210
177
|
puts ""
|
211
178
|
if details[:npm]
|
212
179
|
puts "This package also has an npm package, so we'll link that up as well.".blue
|
213
|
-
stream "cd local/#{gem} && yarn install && npx yalc link && cd ../.. && npx yalc link \"#{details[:npm]}\""
|
180
|
+
stream "cd local/#{gem} && yarn install && npm_config_yes=true npx yalc link && cd ../.. && npm_config_yes=true npx yalc link \"#{details[:npm]}\""
|
214
181
|
|
215
182
|
puts ""
|
216
183
|
puts "And now we're going to watch for any changes you make to the JavaScript and recompile as we go.".blue
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet_train
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.96
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Culver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-07-
|
11
|
+
date: 2022-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: standard
|
@@ -192,34 +192,6 @@ dependencies:
|
|
192
192
|
- - ">="
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '0'
|
195
|
-
- !ruby/object:Gem::Dependency
|
196
|
-
name: pry
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
198
|
-
requirements:
|
199
|
-
- - ">="
|
200
|
-
- !ruby/object:Gem::Version
|
201
|
-
version: '0'
|
202
|
-
type: :runtime
|
203
|
-
prerelease: false
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
205
|
-
requirements:
|
206
|
-
- - ">="
|
207
|
-
- !ruby/object:Gem::Version
|
208
|
-
version: '0'
|
209
|
-
- !ruby/object:Gem::Dependency
|
210
|
-
name: pry-stack_explorer
|
211
|
-
requirement: !ruby/object:Gem::Requirement
|
212
|
-
requirements:
|
213
|
-
- - ">="
|
214
|
-
- !ruby/object:Gem::Version
|
215
|
-
version: '0'
|
216
|
-
type: :runtime
|
217
|
-
prerelease: false
|
218
|
-
version_requirements: !ruby/object:Gem::Requirement
|
219
|
-
requirements:
|
220
|
-
- - ">="
|
221
|
-
- !ruby/object:Gem::Version
|
222
|
-
version: '0'
|
223
195
|
- !ruby/object:Gem::Dependency
|
224
196
|
name: awesome_print
|
225
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -402,6 +374,34 @@ dependencies:
|
|
402
374
|
- - ">="
|
403
375
|
- !ruby/object:Gem::Version
|
404
376
|
version: '0'
|
377
|
+
- !ruby/object:Gem::Dependency
|
378
|
+
name: pry
|
379
|
+
requirement: !ruby/object:Gem::Requirement
|
380
|
+
requirements:
|
381
|
+
- - ">="
|
382
|
+
- !ruby/object:Gem::Version
|
383
|
+
version: '0'
|
384
|
+
type: :development
|
385
|
+
prerelease: false
|
386
|
+
version_requirements: !ruby/object:Gem::Requirement
|
387
|
+
requirements:
|
388
|
+
- - ">="
|
389
|
+
- !ruby/object:Gem::Version
|
390
|
+
version: '0'
|
391
|
+
- !ruby/object:Gem::Dependency
|
392
|
+
name: pry-stack_explorer
|
393
|
+
requirement: !ruby/object:Gem::Requirement
|
394
|
+
requirements:
|
395
|
+
- - ">="
|
396
|
+
- !ruby/object:Gem::Version
|
397
|
+
version: '0'
|
398
|
+
type: :development
|
399
|
+
prerelease: false
|
400
|
+
version_requirements: !ruby/object:Gem::Requirement
|
401
|
+
requirements:
|
402
|
+
- - ">="
|
403
|
+
- !ruby/object:Gem::Version
|
404
|
+
version: '0'
|
405
405
|
- !ruby/object:Gem::Dependency
|
406
406
|
name: devise-pwned_password
|
407
407
|
requirement: !ruby/object:Gem::Requirement
|
@@ -563,6 +563,7 @@ files:
|
|
563
563
|
- config/locales/en/billing/products.en.yml
|
564
564
|
- config/locales/en/devise.en.yml
|
565
565
|
- config/locales/en/doorkeeper.en.yml
|
566
|
+
- config/locales/en/framework_packages.yml
|
566
567
|
- config/locales/en/invitations.en.yml
|
567
568
|
- config/locales/en/memberships.en.yml
|
568
569
|
- config/locales/en/oauth.en.yml
|
@@ -631,6 +632,7 @@ files:
|
|
631
632
|
- docs/seeds.md
|
632
633
|
- docs/super-scaffolding.md
|
633
634
|
- docs/super-scaffolding/delegated-types.md
|
635
|
+
- docs/super-scaffolding/sortable.md
|
634
636
|
- docs/teams.md
|
635
637
|
- docs/testing.md
|
636
638
|
- docs/themes.md
|
@@ -671,7 +673,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
671
673
|
- !ruby/object:Gem::Version
|
672
674
|
version: '0'
|
673
675
|
requirements: []
|
674
|
-
rubygems_version: 3.
|
676
|
+
rubygems_version: 3.3.7
|
675
677
|
signing_key:
|
676
678
|
specification_version: 4
|
677
679
|
summary: Bullet Train
|