From d5e327af539554e6a54ac7622809ccc592a726a1 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Fri, 19 Jun 2026 09:20:07 +0200 Subject: [PATCH 01/22] Move editor to partial --- app/views/ferbe/shared/_editor.html.erb | 32 +++++++++++++++++++ .../ferbe/templates/edit.turbo_stream.erb | 31 +----------------- 2 files changed, 33 insertions(+), 30 deletions(-) create mode 100644 app/views/ferbe/shared/_editor.html.erb diff --git a/app/views/ferbe/shared/_editor.html.erb b/app/views/ferbe/shared/_editor.html.erb new file mode 100644 index 0000000..23ace10 --- /dev/null +++ b/app/views/ferbe/shared/_editor.html.erb @@ -0,0 +1,32 @@ +<%# locals: (template:) %> + +
+
+ +

<%= Pathname.new(template[:path]).relative_path_from(Rails.root) %>

+
+
+<% template[:render_path].each_with_index do |path, level| %>
+<%= " " * level %>> <%= link_to(Pathname.new(path).relative_path_from(Rails.root),
+                                edit_template_url(template: {
+                                  path: path,
+                                  render_path: template[:render_path]
+                                }),
+                                data: { turbo_stream: true }) %>
+<% end %>
+
+ <%= form_with url: template_url, + method: :patch, + scope: :template, + data: { ferbe_editor_target: "form" } do |form| %> + <%= form.hidden_field :path, value: template[:path] %> + <%= form.hidden_field :content, + value: template[:content], + data: { ferbe_editor_target: "input" } %> +
<%= template[:content] %>
+ <%= form.submit "💾", class: "ferbe__button ferbe__save-button" %> + <% end %> +

+
diff --git a/app/views/ferbe/templates/edit.turbo_stream.erb b/app/views/ferbe/templates/edit.turbo_stream.erb index 8a57d47..b7e7312 100644 --- a/app/views/ferbe/templates/edit.turbo_stream.erb +++ b/app/views/ferbe/templates/edit.turbo_stream.erb @@ -1,32 +1,3 @@ <%= turbo_stream.update "ferbe-editor" do %> -
-
- -

<%= Pathname.new(@template[:path]).relative_path_from(Rails.root) %>

-
-
-<% @template[:render_path].each_with_index do |path, level| %>
-<%= " " * level %>> <%= link_to( Pathname.new(path).relative_path_from(Rails.root),
-                                 edit_template_url(template: {
-                                   path: path,
-                                   render_path: @template[:render_path]
-                                 }),
-                                 data: { turbo_stream: true }) %>
-<% end %>
-
- <%= form_with url: template_url, - method: :patch, - scope: :template, - data: { ferbe_editor_target: "form" } do |form| %> - <%= form.hidden_field :path, value: @template[:path] %> - <%= form.hidden_field :content, - value: @template[:content], - data: { ferbe_editor_target: "input" } %> -
<%= @template[:content] %>
- <%= form.submit "💾", class: "ferbe__button ferbe__save-button" %> - <% end %> -

-  
+ <%= render partial: "ferbe/shared/editor", locals: { template: @template } %> <% end %> From f8cb52f580e65c6c1b2cb7788b24bd9061adaad6 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Fri, 19 Jun 2026 09:20:30 +0200 Subject: [PATCH 02/22] Adjust styles --- app/assets/stylesheets/ferbe/application.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css index d7ddf6c..63e42e6 100644 --- a/app/assets/stylesheets/ferbe/application.css +++ b/app/assets/stylesheets/ferbe/application.css @@ -9,16 +9,16 @@ .ferbe__page { display: flex; - height: 100vh; - - & > main { - flex-grow: 1; - } + min-height: 100vh; .ferbe__editor * { all: revert; } + ferbe-template { + display: contents; + } + .ferbe__editor > div { width: min(50vw, 60rem); padding: 1rem; From 5bb286adf4c3956543379aff4e1f5a09b7937814 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Fri, 19 Jun 2026 13:33:20 +0200 Subject: [PATCH 03/22] Add iframe for page --- .../ferbe/elements/ferbe_template.js | 15 ++++++++------ app/assets/stylesheets/ferbe/application.css | 4 ++++ app/controllers/ferbe/editor_controller.rb | 20 +++++++++++++++++++ app/views/ferbe/editor/show.html.erb | 11 ++++++++++ app/views/layouts/ferbe/application.html.erb | 17 ++++++++++++++++ config/routes.rb | 2 ++ 6 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 app/controllers/ferbe/editor_controller.rb create mode 100644 app/views/ferbe/editor/show.html.erb create mode 100644 app/views/layouts/ferbe/application.html.erb diff --git a/app/assets/javascripts/ferbe/elements/ferbe_template.js b/app/assets/javascripts/ferbe/elements/ferbe_template.js index 0a957e8..a03476c 100644 --- a/app/assets/javascripts/ferbe/elements/ferbe_template.js +++ b/app/assets/javascripts/ferbe/elements/ferbe_template.js @@ -47,15 +47,18 @@ export default class FerbeTemplate extends HTMLElement { const renderPath = this.#getRenderPath(); const params = new URLSearchParams(); + params.append("url", window.location.toString()) params.append("template[path]", path); renderPath.forEach((p) => params.append("template[render_path][]", p)); - fetch(`/ferbe/template/edit?${params.toString()}`, { - headers: { Accept: "text/vnd.turbo-stream.html" }, - }) - .then((r) => r.text()) - .then((html) => Turbo.renderStreamMessage(html)) - .catch((err) => console.error("Failed to open editor:", err)); + window.parent.location.href = `/ferbe/editor?${params.toString()}`; + + // fetch(`/ferbe/editor?${params.toString()}`, { + // headers: { Accept: "text/vnd.turbo-stream.html" }, + // }) + // .then((r) => r.text()) + // .then((html) => Turbo.renderStreamMessage(html)) + // .catch((err) => console.error("Failed to open editor:", err)); } #getRenderPath() { diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css index 63e42e6..aff3109 100644 --- a/app/assets/stylesheets/ferbe/application.css +++ b/app/assets/stylesheets/ferbe/application.css @@ -11,6 +11,10 @@ display: flex; min-height: 100vh; + iframe { + flex-grow: 1; + } + .ferbe__editor * { all: revert; } diff --git a/app/controllers/ferbe/editor_controller.rb b/app/controllers/ferbe/editor_controller.rb new file mode 100644 index 0000000..8dd079f --- /dev/null +++ b/app/controllers/ferbe/editor_controller.rb @@ -0,0 +1,20 @@ +module Ferbe + class EditorController < ApplicationController + def show + @template = { + content: File.read(editor_params[:template][:path]), + path: editor_params[:template][:path], + render_path: editor_params[:template][:render_path] + } + + @url = editor_params[:url] + end + + def editor_params + { + template: params.require(:template).permit(:path, render_path: []), + url: params.require(:url) + } + end + end +end diff --git a/app/views/ferbe/editor/show.html.erb b/app/views/ferbe/editor/show.html.erb new file mode 100644 index 0000000..e97bcbe --- /dev/null +++ b/app/views/ferbe/editor/show.html.erb @@ -0,0 +1,11 @@ +
+ + <%= tag.div class: "ferbe__editor", id: "ferbe-editor", data: { + modifier_key: Ferbe.configuration.modifier_key, + use_local_editor: Ferbe.configuration.use_local_editor, + turbo_permanent: true + } do %> + <%= render partial: "ferbe/shared/editor", locals: { template: @template } %> + <% end %> + +
diff --git a/app/views/layouts/ferbe/application.html.erb b/app/views/layouts/ferbe/application.html.erb new file mode 100644 index 0000000..ebc40f7 --- /dev/null +++ b/app/views/layouts/ferbe/application.html.erb @@ -0,0 +1,17 @@ + + + + Ferbe + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= yield :head %> + + <%= stylesheet_link_tag "ferbe/application", media: "all" %> + + + +<%= yield %> + + + diff --git a/config/routes.rb b/config/routes.rb index c8c13d2..64f1d25 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,4 +2,6 @@ resource :template, only: [:edit, :update] do post :edit_locally, on: :collection end + + resource :editor, only: :show, controller: :editor end From edead94340e9047c8614b104d97b13d320e8bed0 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Mon, 22 Jun 2026 11:07:35 +0200 Subject: [PATCH 04/22] Add correct styles + js --- app/views/layouts/ferbe/application.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/ferbe/application.html.erb b/app/views/layouts/ferbe/application.html.erb index ebc40f7..ddd97bf 100644 --- a/app/views/layouts/ferbe/application.html.erb +++ b/app/views/layouts/ferbe/application.html.erb @@ -7,7 +7,9 @@ <%= yield :head %> - <%= stylesheet_link_tag "ferbe/application", media: "all" %> + <%= ferbe_styles_tag %> + <%= javascript_importmap_tags %> + <%= ferbe_javascript_tag %> From 87ee9f60191deb43da3ac3fefb37c7fca6e368fe Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Mon, 22 Jun 2026 11:19:55 +0200 Subject: [PATCH 05/22] Adjust editor to iframe approach --- .../controllers/ferbe_editor_controller.js | 30 ++++--------------- app/assets/stylesheets/ferbe/application.css | 13 ++------ app/views/ferbe/shared/_editor.html.erb | 1 - 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js index 1030b64..7705c4f 100644 --- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js +++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js @@ -1,18 +1,17 @@ -import { Controller } from "@hotwired/stimulus"; -import { CodeJar } from "codejar"; +import {Controller} from "@hotwired/stimulus"; +import {CodeJar} from "codejar"; import hljs from "highlight.js/lib/core"; import erb from "highlight.js/lib/languages/erb"; import xml from "highlight.js/lib/languages/xml"; import ruby from "highlight.js/lib/languages/ruby"; export default class FerbeEditorController extends Controller { - static targets = ["editor", "form", "input", "errorContainer"]; + static targets = ["editor", "form", "input"]; connect() { this.#setupHighlighting(); this.#highlight(this.editorTarget); this.#preventUnsavedClosing(); - this.#setupErrorHandling(); } disconnect() { @@ -22,7 +21,8 @@ export default class FerbeEditorController extends Controller { } close() { - this.element.remove(); + const params = new URLSearchParams(document.location.search); + window.location = params.get("url"); } save() { @@ -58,24 +58,4 @@ export default class FerbeEditorController extends Controller { return "There are unsaved changes in the ferbe editor."; }; } - - #setupErrorHandling() { - addEventListener("turbo:before-fetch-response", (event) => { - const response = event.detail.fetchResponse; - if (response.statusCode !== 500) return; - - event.preventDefault(); - document.documentElement.removeAttribute("aria-busy"); - this.#displayError({ - message: `${response.statusCode} ${response.response.statusText}`, - url: response.response.url, - }); - }); - } - - #displayError({ message, url }) { - this.errorContainerTarget.innerHTML = `There is an error that was likely caused by your edit: -${message} -Open in new tab`; - } } diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css index aff3109..a6094c6 100644 --- a/app/assets/stylesheets/ferbe/application.css +++ b/app/assets/stylesheets/ferbe/application.css @@ -11,8 +11,8 @@ display: flex; min-height: 100vh; - iframe { - flex-grow: 1; + iframe { + flex-grow: 1; } .ferbe__editor * { @@ -87,14 +87,5 @@ } } } - - .ferbe__error-container { - border-color: var(--ferbe-color-red); - background-color: var(--ferbe-color-light-red); - - &:empty { - display: none; - } - } } } diff --git a/app/views/ferbe/shared/_editor.html.erb b/app/views/ferbe/shared/_editor.html.erb index 23ace10..2d4e69c 100644 --- a/app/views/ferbe/shared/_editor.html.erb +++ b/app/views/ferbe/shared/_editor.html.erb @@ -28,5 +28,4 @@ keydown.meta+s->ferbe-editor#save:prevent"><%= template[:content] %> <%= form.submit "💾", class: "ferbe__button ferbe__save-button" %> <% end %> -

 

From faf2d98837baac1e4acf16992fc917649c9f7b1a Mon Sep 17 00:00:00 2001
From: Tim Landolt 
Date: Mon, 22 Jun 2026 11:33:01 +0200
Subject: [PATCH 06/22] Clean up css

---
 app/assets/stylesheets/ferbe/application.css | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css
index a6094c6..837cf76 100644
--- a/app/assets/stylesheets/ferbe/application.css
+++ b/app/assets/stylesheets/ferbe/application.css
@@ -2,23 +2,21 @@
   --ferbe-color-light-grey: #fafafa;
   --ferbe-color-grey: #dedede;
   --ferbe-color-blue: #0033b3;
-  --ferbe-color-red: #f6817f;
-  --ferbe-color-light-red: #ff9b99;
   --ferbe-border: 2px solid var(--ferbe-color-grey);
 }
+body {
+  margin: 0;
+}
 
 .ferbe__page {
   display: flex;
   min-height: 100vh;
 
   iframe {
+    all: unset;
     flex-grow: 1;
   }
 
-  .ferbe__editor * {
-    all: revert;
-  }
-
   ferbe-template {
     display: contents;
   }

From 53769c5633d82e6379ab3ad92c1d707fe10d88eb Mon Sep 17 00:00:00 2001
From: Tim Landolt 
Date: Mon, 22 Jun 2026 15:07:25 +0200
Subject: [PATCH 07/22] Make new editor useable

---
 app/assets/javascripts/ferbe/application.js   | 19 ++++++++++++++
 .../controllers/ferbe_editor_controller.js    | 23 +++++++++++++++--
 .../ferbe/elements/ferbe_template.js          | 25 +++++++------------
 app/assets/stylesheets/ferbe/application.css  |  1 +
 app/views/ferbe/editor/show.html.erb          |  5 ++--
 app/views/ferbe/shared/_editor.html.erb       |  3 ++-
 .../app/views/layouts/application.html.erb    |  4 ---
 7 files changed, 54 insertions(+), 26 deletions(-)

diff --git a/app/assets/javascripts/ferbe/application.js b/app/assets/javascripts/ferbe/application.js
index 33219c8..56651b0 100644
--- a/app/assets/javascripts/ferbe/application.js
+++ b/app/assets/javascripts/ferbe/application.js
@@ -1,3 +1,22 @@
 import FerbeTemplate from "ferbe/elements/ferbe_template";
 
 customElements.define("ferbe-template", FerbeTemplate);
+
+addEventListener("ferbe:open-editor", (event) => {
+  const editor = document.getElementById("ferbe-editor");
+  if (editor) return;
+
+  openEditor(event.detail);
+});
+
+function openEditor(template) {
+  const params = new URLSearchParams();
+
+  params.append("url", template.url);
+  params.append("template[path]", template.filePath);
+  template.renderPath.forEach((p) =>
+    params.append("template[render_path][]", p),
+  );
+
+  window.location.href = `/ferbe/editor?${params.toString()}`;
+}
diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js
index 7705c4f..0d865ea 100644
--- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js
+++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js
@@ -1,5 +1,5 @@
-import {Controller} from "@hotwired/stimulus";
-import {CodeJar} from "codejar";
+import { Controller } from "@hotwired/stimulus";
+import { CodeJar } from "codejar";
 import hljs from "highlight.js/lib/core";
 import erb from "highlight.js/lib/languages/erb";
 import xml from "highlight.js/lib/languages/xml";
@@ -20,6 +20,25 @@ export default class FerbeEditorController extends Controller {
     this.errorContainerTarget.innerHTML = "";
   }
 
+  open(event) {
+    const template = event.detail;
+
+    const params = new URLSearchParams();
+    params.append("template[path]", template.filePath);
+    template.renderPath.forEach((p) =>
+      params.append("template[render_path][]", p),
+    );
+
+    fetch(`/ferbe/template/edit?${params.toString()}`, {
+      headers: { Accept: "text/vnd.turbo-stream.html" },
+    })
+      .then((r) => r.text())
+      .then((html) => {
+        Turbo.renderStreamMessage(html);
+      })
+      .catch((err) => console.error("Failed to open editor:", err));
+  }
+
   close() {
     const params = new URLSearchParams(document.location.search);
     window.location = params.get("url");
diff --git a/app/assets/javascripts/ferbe/elements/ferbe_template.js b/app/assets/javascripts/ferbe/elements/ferbe_template.js
index a03476c..6579848 100644
--- a/app/assets/javascripts/ferbe/elements/ferbe_template.js
+++ b/app/assets/javascripts/ferbe/elements/ferbe_template.js
@@ -43,22 +43,15 @@ export default class FerbeTemplate extends HTMLElement {
   }
 
   #openInEditor() {
-    const path = this.getAttribute("path");
-    const renderPath = this.#getRenderPath();
-
-    const params = new URLSearchParams();
-    params.append("url", window.location.toString())
-    params.append("template[path]", path);
-    renderPath.forEach((p) => params.append("template[render_path][]", p));
-
-    window.parent.location.href = `/ferbe/editor?${params.toString()}`;
-
-    // fetch(`/ferbe/editor?${params.toString()}`, {
-    //   headers: { Accept: "text/vnd.turbo-stream.html" },
-    // })
-    //   .then((r) => r.text())
-    //   .then((html) => Turbo.renderStreamMessage(html))
-    //   .catch((err) => console.error("Failed to open editor:", err));
+    window.top.dispatchEvent(
+      new CustomEvent("ferbe:open-editor", {
+        detail: {
+          url: window.location.toString(),
+          filePath: this.getAttribute("path"),
+          renderPath: this.#getRenderPath(),
+        },
+      }),
+    );
   }
 
   #getRenderPath() {
diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css
index 837cf76..4878c30 100644
--- a/app/assets/stylesheets/ferbe/application.css
+++ b/app/assets/stylesheets/ferbe/application.css
@@ -4,6 +4,7 @@
   --ferbe-color-blue: #0033b3;
   --ferbe-border: 2px solid var(--ferbe-color-grey);
 }
+
 body {
   margin: 0;
 }
diff --git a/app/views/ferbe/editor/show.html.erb b/app/views/ferbe/editor/show.html.erb
index e97bcbe..7ad5966 100644
--- a/app/views/ferbe/editor/show.html.erb
+++ b/app/views/ferbe/editor/show.html.erb
@@ -1,11 +1,10 @@
-
+
- <%= tag.div class: "ferbe__editor", id: "ferbe-editor", data: { + <%= tag.div class: "ferbe__editor", id: "ferbe-editor", data: { modifier_key: Ferbe.configuration.modifier_key, use_local_editor: Ferbe.configuration.use_local_editor, turbo_permanent: true } do %> <%= render partial: "ferbe/shared/editor", locals: { template: @template } %> <% end %> -
diff --git a/app/views/ferbe/shared/_editor.html.erb b/app/views/ferbe/shared/_editor.html.erb index 2d4e69c..16bbb0a 100644 --- a/app/views/ferbe/shared/_editor.html.erb +++ b/app/views/ferbe/shared/_editor.html.erb @@ -1,6 +1,7 @@ <%# locals: (template:) %> -
+

<%= Pathname.new(template[:path]).relative_path_from(Rails.root) %>

diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 74a52bd..5e7e2f1 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -25,12 +25,8 @@ -
<%= yield %>
- - <%= ferbe_editor_tag %> -
From 821e3d43ae58e3b389175306a60cf154c5d83766 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Mon, 22 Jun 2026 16:29:44 +0200 Subject: [PATCH 08/22] Update search bar when selecting new partial --- app/assets/javascripts/ferbe/application.js | 11 ++--------- .../ferbe/controllers/ferbe_editor_controller.js | 3 ++- app/assets/javascripts/ferbe/utils.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/ferbe/utils.js diff --git a/app/assets/javascripts/ferbe/application.js b/app/assets/javascripts/ferbe/application.js index 56651b0..4177fa4 100644 --- a/app/assets/javascripts/ferbe/application.js +++ b/app/assets/javascripts/ferbe/application.js @@ -1,4 +1,5 @@ import FerbeTemplate from "ferbe/elements/ferbe_template"; +import { editorUrl } from "ferbe/utils"; customElements.define("ferbe-template", FerbeTemplate); @@ -10,13 +11,5 @@ addEventListener("ferbe:open-editor", (event) => { }); function openEditor(template) { - const params = new URLSearchParams(); - - params.append("url", template.url); - params.append("template[path]", template.filePath); - template.renderPath.forEach((p) => - params.append("template[render_path][]", p), - ); - - window.location.href = `/ferbe/editor?${params.toString()}`; + window.location.href = editorUrl(template); } diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js index 0d865ea..beab3b6 100644 --- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js +++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js @@ -4,6 +4,7 @@ import hljs from "highlight.js/lib/core"; import erb from "highlight.js/lib/languages/erb"; import xml from "highlight.js/lib/languages/xml"; import ruby from "highlight.js/lib/languages/ruby"; +import { editorUrl } from "ferbe/utils"; export default class FerbeEditorController extends Controller { static targets = ["editor", "form", "input"]; @@ -17,7 +18,6 @@ export default class FerbeEditorController extends Controller { disconnect() { this.jar.destroy(); window.onbeforeunload = null; - this.errorContainerTarget.innerHTML = ""; } open(event) { @@ -35,6 +35,7 @@ export default class FerbeEditorController extends Controller { .then((r) => r.text()) .then((html) => { Turbo.renderStreamMessage(html); + window.history.pushState({}, "", editorUrl(template)); }) .catch((err) => console.error("Failed to open editor:", err)); } diff --git a/app/assets/javascripts/ferbe/utils.js b/app/assets/javascripts/ferbe/utils.js new file mode 100644 index 0000000..cd542dd --- /dev/null +++ b/app/assets/javascripts/ferbe/utils.js @@ -0,0 +1,11 @@ +export function editorUrl(template) { + const params = new URLSearchParams(); + + params.append("url", template.url); + params.append("template[path]", template.filePath); + template.renderPath.forEach((p) => + params.append("template[render_path][]", p), + ); + + return `/ferbe/editor?${params.toString()}`; +} From 79dc58142b5f76b5c7989d99e60bc846f0e231d8 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 07:45:55 +0200 Subject: [PATCH 09/22] Add editor test --- .../ferbe/application_controller.rb | 13 +++++++++++ app/controllers/ferbe/editor_controller.rb | 9 +++++--- app/controllers/ferbe/templates_controller.rb | 14 ----------- .../ferbe/editor_controller_test.rb | 23 +++++++++++++++++++ 4 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 test/controllers/ferbe/editor_controller_test.rb diff --git a/app/controllers/ferbe/application_controller.rb b/app/controllers/ferbe/application_controller.rb index 7924741..e2905c1 100644 --- a/app/controllers/ferbe/application_controller.rb +++ b/app/controllers/ferbe/application_controller.rb @@ -1,4 +1,17 @@ module Ferbe class ApplicationController < ::ApplicationController + private + + def valid_path?(path) + expanded_path = File.expand_path path + + File.readable?(expanded_path) && + view_path?(expanded_path) && + expanded_path.ends_with?(".erb") + end + + def view_path?(path) + view_paths.any? { |view_path| path.starts_with?(view_path.to_s) } + end end end diff --git a/app/controllers/ferbe/editor_controller.rb b/app/controllers/ferbe/editor_controller.rb index 8dd079f..0179b6f 100644 --- a/app/controllers/ferbe/editor_controller.rb +++ b/app/controllers/ferbe/editor_controller.rb @@ -1,10 +1,13 @@ module Ferbe class EditorController < ApplicationController def show + template = editor_params[:template] + return head :bad_request unless valid_path? template[:path] + @template = { - content: File.read(editor_params[:template][:path]), - path: editor_params[:template][:path], - render_path: editor_params[:template][:render_path] + content: File.read(template[:path]), + path: template[:path], + render_path: template[:render_path] } @url = editor_params[:url] diff --git a/app/controllers/ferbe/templates_controller.rb b/app/controllers/ferbe/templates_controller.rb index 59be321..27ba6c2 100644 --- a/app/controllers/ferbe/templates_controller.rb +++ b/app/controllers/ferbe/templates_controller.rb @@ -37,19 +37,5 @@ def update File.write(template_params[:path], template_params[:content]) render turbo_stream: turbo_stream.action(:refresh, "") end - - private - - def valid_path?(path) - expanded_path = File.expand_path path - - File.readable?(expanded_path) && - view_path?(expanded_path) && - expanded_path.ends_with?(".erb") - end - - def view_path?(path) - view_paths.any? { |view_path| path.starts_with?(view_path.to_s) } - end end end diff --git a/test/controllers/ferbe/editor_controller_test.rb b/test/controllers/ferbe/editor_controller_test.rb new file mode 100644 index 0000000..821bd0d --- /dev/null +++ b/test/controllers/ferbe/editor_controller_test.rb @@ -0,0 +1,23 @@ +require "test_helper" +require "tempfile" + +module Ferbe + class EditorControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + + test "rejection of editor for invalid template" do + invalid_path = "some/invalid/file.txt" + get editor_url, params: {template: {path: invalid_path, render_path: [invalid_path]}, url: "http://example.com"} + assert_response :bad_request + end + + test "success for valid template" do + Tempfile.create(["test", ".html.erb"], Rails.root.join("app/views")) do |file| + file.write "I am a template!" + + get editor_url, params: {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"} + assert_response :success + end + end + end +end From a51c075c3c77793d085575af4cafcf3baa92194c Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 08:31:13 +0200 Subject: [PATCH 10/22] Reformat show template --- app/views/ferbe/editor/show.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/ferbe/editor/show.html.erb b/app/views/ferbe/editor/show.html.erb index 7ad5966..a03c1d6 100644 --- a/app/views/ferbe/editor/show.html.erb +++ b/app/views/ferbe/editor/show.html.erb @@ -1,6 +1,7 @@
- <%= tag.div class: "ferbe__editor", id: "ferbe-editor", data: { + + <%= tag.div class: "ferbe__editor", id: "ferbe-editor", data: { modifier_key: Ferbe.configuration.modifier_key, use_local_editor: Ferbe.configuration.use_local_editor, turbo_permanent: true From 102d320eab4388556a37b063afd5637f2cb85fb5 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 10:54:34 +0200 Subject: [PATCH 11/22] Separate js cleanly --- app/assets/javascripts/ferbe/application.js | 18 +++++------------- .../controllers/ferbe_editor_controller.js | 2 +- app/assets/javascripts/ferbe/host.js | 5 +++++ .../ferbe/{utils.js => utils/editor_opener.js} | 13 +++++++++++++ app/views/layouts/ferbe/application.html.erb | 6 ++++-- config/ferbe_importmap.rb | 1 - lib/ferbe/helper.rb | 10 +--------- test/dummy/app/javascript/controllers/index.js | 2 -- .../app/views/layouts/application.html.erb | 1 - 9 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 app/assets/javascripts/ferbe/host.js rename app/assets/javascripts/ferbe/{utils.js => utils/editor_opener.js} (50%) diff --git a/app/assets/javascripts/ferbe/application.js b/app/assets/javascripts/ferbe/application.js index 4177fa4..424f0df 100644 --- a/app/assets/javascripts/ferbe/application.js +++ b/app/assets/javascripts/ferbe/application.js @@ -1,15 +1,7 @@ -import FerbeTemplate from "ferbe/elements/ferbe_template"; -import { editorUrl } from "ferbe/utils"; +import { registerOpeningListener } from "ferbe/utils/editor_opener"; +import { application } from "controllers/application"; +import FerbeEditorController from "ferbe/controllers/ferbe_editor_controller"; -customElements.define("ferbe-template", FerbeTemplate); +registerOpeningListener(); -addEventListener("ferbe:open-editor", (event) => { - const editor = document.getElementById("ferbe-editor"); - if (editor) return; - - openEditor(event.detail); -}); - -function openEditor(template) { - window.location.href = editorUrl(template); -} +application.register("ferbe-editor", FerbeEditorController); diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js index beab3b6..ca07b61 100644 --- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js +++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js @@ -4,7 +4,7 @@ import hljs from "highlight.js/lib/core"; import erb from "highlight.js/lib/languages/erb"; import xml from "highlight.js/lib/languages/xml"; import ruby from "highlight.js/lib/languages/ruby"; -import { editorUrl } from "ferbe/utils"; +import { editorUrl } from "ferbe/utils/editor_opener"; export default class FerbeEditorController extends Controller { static targets = ["editor", "form", "input"]; diff --git a/app/assets/javascripts/ferbe/host.js b/app/assets/javascripts/ferbe/host.js new file mode 100644 index 0000000..0fe60a0 --- /dev/null +++ b/app/assets/javascripts/ferbe/host.js @@ -0,0 +1,5 @@ +import FerbeTemplate from "ferbe/elements/ferbe_template"; +import { registerOpeningListener } from "ferbe/utils/editor_opener"; + +customElements.define("ferbe-template", FerbeTemplate); +registerOpeningListener(); diff --git a/app/assets/javascripts/ferbe/utils.js b/app/assets/javascripts/ferbe/utils/editor_opener.js similarity index 50% rename from app/assets/javascripts/ferbe/utils.js rename to app/assets/javascripts/ferbe/utils/editor_opener.js index cd542dd..f3c3c90 100644 --- a/app/assets/javascripts/ferbe/utils.js +++ b/app/assets/javascripts/ferbe/utils/editor_opener.js @@ -1,3 +1,16 @@ +export function registerOpeningListener() { + window.addEventListener("ferbe:open-editor", (event) => { + const editor = document.getElementById("ferbe-editor"); + if (editor) return; + + openEditor(event.detail); + }); +} + +function openEditor(template) { + window.location.href = editorUrl(template); +} + export function editorUrl(template) { const params = new URLSearchParams(); diff --git a/app/views/layouts/ferbe/application.html.erb b/app/views/layouts/ferbe/application.html.erb index ddd97bf..6c16274 100644 --- a/app/views/layouts/ferbe/application.html.erb +++ b/app/views/layouts/ferbe/application.html.erb @@ -7,9 +7,11 @@ <%= yield :head %> - <%= ferbe_styles_tag %> <%= javascript_importmap_tags %> - <%= ferbe_javascript_tag %> + <%= javascript_import_module_tag "ferbe/application" %> + + <%= stylesheet_link_tag "ferbe/highlight", media: "all" %> + <%= stylesheet_link_tag "ferbe/application", media: "all" %> diff --git a/config/ferbe_importmap.rb b/config/ferbe_importmap.rb index b130484..c86fa67 100644 --- a/config/ferbe_importmap.rb +++ b/config/ferbe_importmap.rb @@ -4,5 +4,4 @@ pin "highlight.js/lib/languages/ruby", to: "https://ga.jspm.io/npm:highlight.js@11.11.1/es/languages/ruby.js" pin "codejar", to: "https://ga.jspm.io/npm:codejar@4.2.0/dist/codejar.js" -pin "ferbe", to: "ferbe/application.js" pin_all_from Ferbe::Engine.root.join("app/assets/javascripts/ferbe"), under: "ferbe" diff --git a/lib/ferbe/helper.rb b/lib/ferbe/helper.rb index ad6771f..7158036 100644 --- a/lib/ferbe/helper.rb +++ b/lib/ferbe/helper.rb @@ -1,19 +1,11 @@ module Ferbe module Helper #:nocov: -> tested implicitly through all manual system tests - def ferbe_styles_tag - return unless Ferbe.configuration.enabled - - capture do - concat stylesheet_link_tag "ferbe/highlight", media: "all" - concat stylesheet_link_tag "ferbe/application", media: "all" - end - end def ferbe_javascript_tag return unless Ferbe.configuration.enabled - javascript_import_module_tag "ferbe" + javascript_import_module_tag "ferbe/host" end def ferbe_editor_tag diff --git a/test/dummy/app/javascript/controllers/index.js b/test/dummy/app/javascript/controllers/index.js index f913136..625b998 100644 --- a/test/dummy/app/javascript/controllers/index.js +++ b/test/dummy/app/javascript/controllers/index.js @@ -1,7 +1,5 @@ // Import and register all your controllers from the importmap via controllers/**/*_controller import { application } from "controllers/application"; import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"; -import FerbeEditorController from "ferbe/controllers/ferbe_editor_controller"; eagerLoadControllersFrom("controllers", application); -application.register("ferbe-editor", FerbeEditorController); diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 5e7e2f1..716d990 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -19,7 +19,6 @@ <%= stylesheet_link_tag :app %> - <%= ferbe_styles_tag %> <%= javascript_importmap_tags %> <%= ferbe_javascript_tag %> From 2f22c9fa35af15ca6be440b1eb25a53416a13ec6 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 11:07:48 +0200 Subject: [PATCH 12/22] Set display contents on template elements --- app/assets/stylesheets/ferbe/application.css | 4 ---- app/assets/stylesheets/ferbe/host.css | 3 +++ lib/ferbe/helper.rb | 5 +++++ test/dummy/app/views/layouts/application.html.erb | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 app/assets/stylesheets/ferbe/host.css diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css index 4878c30..f0228bf 100644 --- a/app/assets/stylesheets/ferbe/application.css +++ b/app/assets/stylesheets/ferbe/application.css @@ -18,10 +18,6 @@ body { flex-grow: 1; } - ferbe-template { - display: contents; - } - .ferbe__editor > div { width: min(50vw, 60rem); padding: 1rem; diff --git a/app/assets/stylesheets/ferbe/host.css b/app/assets/stylesheets/ferbe/host.css new file mode 100644 index 0000000..30b746d --- /dev/null +++ b/app/assets/stylesheets/ferbe/host.css @@ -0,0 +1,3 @@ +ferbe-template { + display: contents; +} diff --git a/lib/ferbe/helper.rb b/lib/ferbe/helper.rb index 7158036..5d8830e 100644 --- a/lib/ferbe/helper.rb +++ b/lib/ferbe/helper.rb @@ -1,6 +1,11 @@ module Ferbe module Helper #:nocov: -> tested implicitly through all manual system tests + def ferbe_styles_tag + return unless Ferbe.configuration.enabled + + stylesheet_link_tag "ferbe/host", media: "all" + end def ferbe_javascript_tag return unless Ferbe.configuration.enabled diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 716d990..d6ada37 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -19,6 +19,8 @@ <%= stylesheet_link_tag :app %> + <%= ferbe_styles_tag %> + <%= javascript_importmap_tags %> <%= ferbe_javascript_tag %> From c5f61854baca7a99d1f19db1b76e0509b4c5b32f Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 11:27:55 +0200 Subject: [PATCH 13/22] Add demo for display contents --- .../app/assets/stylesheets/application.css | 25 ++++++++++++------- test/dummy/app/views/home/index.html.erb | 2 ++ test/dummy/app/views/shared/_grid.html.erb | 5 ++++ .../shared/_horizontal_traffic_light.html.erb | 2 +- test/dummy/app/views/shared/_tower.html.erb | 2 +- .../app/views/shared/_traffic_light.html.erb | 2 +- .../views/shared/colors/_two_brown.html.erb | 2 ++ 7 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 test/dummy/app/views/shared/_grid.html.erb create mode 100644 test/dummy/app/views/shared/colors/_two_brown.html.erb diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css index 1a1bd79..0556e93 100644 --- a/test/dummy/app/assets/stylesheets/application.css +++ b/test/dummy/app/assets/stylesheets/application.css @@ -7,6 +7,10 @@ main { margin-block: 1rem; padding: 1rem; min-height: 3rem; + + &.is-outlined { + border: 0.25rem solid; + } } .red { @@ -24,19 +28,22 @@ main { opacity: 0.5; } -.traffic-light { - border: 0.25rem solid; +.brown { + background-color: var(--primary); + opacity: 0.5; +} - &.is-horizontal { - display: flex; - gap: 1rem; +.is-horizontal { + display: flex; + gap: 1rem; - * { - flex-grow: 1; - } + * { + flex-grow: 1; } } -.tower { +.grid { + display: grid; + grid-template-columns: 1fr 1fr; border: 0.25rem solid; } diff --git a/test/dummy/app/views/home/index.html.erb b/test/dummy/app/views/home/index.html.erb index 9986507..636f278 100644 --- a/test/dummy/app/views/home/index.html.erb +++ b/test/dummy/app/views/home/index.html.erb @@ -1,5 +1,7 @@

Welcome to the Homepage

+<%= render partial: "shared/grid" %> + <%= render partial: "shared/horizontal_traffic_light" %> <%= render partial: "shared/traffic_light" %> diff --git a/test/dummy/app/views/shared/_grid.html.erb b/test/dummy/app/views/shared/_grid.html.erb new file mode 100644 index 0000000..9698a3a --- /dev/null +++ b/test/dummy/app/views/shared/_grid.html.erb @@ -0,0 +1,5 @@ +
+ <%= render partial: "shared/colors/red" %> + <%= render partial: "shared/colors/two_brown" %> + <%= render partial: "shared/colors/green" %> +
diff --git a/test/dummy/app/views/shared/_horizontal_traffic_light.html.erb b/test/dummy/app/views/shared/_horizontal_traffic_light.html.erb index c52a3cc..d25cdb8 100644 --- a/test/dummy/app/views/shared/_horizontal_traffic_light.html.erb +++ b/test/dummy/app/views/shared/_horizontal_traffic_light.html.erb @@ -1,4 +1,4 @@ -
+
<%= render partial: "shared/colors/red" %> <%= render partial: "shared/colors/orange" %> <%= render partial: "shared/colors/green" %> diff --git a/test/dummy/app/views/shared/_tower.html.erb b/test/dummy/app/views/shared/_tower.html.erb index dcfffad..cb7c60b 100644 --- a/test/dummy/app/views/shared/_tower.html.erb +++ b/test/dummy/app/views/shared/_tower.html.erb @@ -1,6 +1,6 @@ <%# locals: (counter:) %> <% counter -= 1 %> -
+
<%= render partial: "shared/tower", locals: { counter: counter } unless counter <= 0 %>
diff --git a/test/dummy/app/views/shared/_traffic_light.html.erb b/test/dummy/app/views/shared/_traffic_light.html.erb index b2122b0..d939fb2 100644 --- a/test/dummy/app/views/shared/_traffic_light.html.erb +++ b/test/dummy/app/views/shared/_traffic_light.html.erb @@ -1,4 +1,4 @@ -
+
<%= render partial: "shared/colors/red" %> <%= render partial: "shared/colors/orange" %> <%= render partial: "shared/colors/green" %> diff --git a/test/dummy/app/views/shared/colors/_two_brown.html.erb b/test/dummy/app/views/shared/colors/_two_brown.html.erb new file mode 100644 index 0000000..5980136 --- /dev/null +++ b/test/dummy/app/views/shared/colors/_two_brown.html.erb @@ -0,0 +1,2 @@ +
+
From 70e0abcee190badfcb7e2d715b3d46e0c504d6bb Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 12:13:47 +0200 Subject: [PATCH 14/22] Use only one controller --- .../controllers/ferbe_editor_controller.js | 4 ++++ .../javascripts/ferbe/utils/editor_opener.js | 2 +- app/controllers/ferbe/templates_controller.rb | 24 ++++++++++++------- .../{shared => templates}/_editor.html.erb | 0 .../show.html.erb => templates/edit.html.erb} | 2 +- .../ferbe/templates/edit.turbo_stream.erb | 3 ++- config/routes.rb | 2 -- 7 files changed, 23 insertions(+), 14 deletions(-) rename app/views/ferbe/{shared => templates}/_editor.html.erb (100%) rename app/views/ferbe/{editor/show.html.erb => templates/edit.html.erb} (78%) diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js index ca07b61..b466af7 100644 --- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js +++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js @@ -23,7 +23,11 @@ export default class FerbeEditorController extends Controller { open(event) { const template = event.detail; + const currentParams = new URLSearchParams(document.location.search); + const url = currentParams.get("url"); + const params = new URLSearchParams(); + params.append("url", url); params.append("template[path]", template.filePath); template.renderPath.forEach((p) => params.append("template[render_path][]", p), diff --git a/app/assets/javascripts/ferbe/utils/editor_opener.js b/app/assets/javascripts/ferbe/utils/editor_opener.js index f3c3c90..caca7a9 100644 --- a/app/assets/javascripts/ferbe/utils/editor_opener.js +++ b/app/assets/javascripts/ferbe/utils/editor_opener.js @@ -20,5 +20,5 @@ export function editorUrl(template) { params.append("template[render_path][]", p), ); - return `/ferbe/editor?${params.toString()}`; + return `/ferbe/template/edit?${params.toString()}`; } diff --git a/app/controllers/ferbe/templates_controller.rb b/app/controllers/ferbe/templates_controller.rb index 27ba6c2..b20f1ba 100644 --- a/app/controllers/ferbe/templates_controller.rb +++ b/app/controllers/ferbe/templates_controller.rb @@ -1,19 +1,16 @@ module Ferbe class TemplatesController < ApplicationController def edit - template_params = params.require(:template).permit(:path, render_path: []) - return head :bad_request unless valid_path? template_params[:path] + template = edit_params[:template] + return head :bad_request unless valid_path? template[:path] @template = { - content: File.read(template_params[:path]), - path: template_params[:path], - render_path: template_params[:render_path] + content: File.read(template[:path]), + path: template[:path], + render_path: template[:render_path] } - respond_to do |format| - format.turbo_stream - format.html { head :no_content } - end + @url = edit_params[:url] end # :nocov: -> covered by manual system tests @@ -37,5 +34,14 @@ def update File.write(template_params[:path], template_params[:content]) render turbo_stream: turbo_stream.action(:refresh, "") end + + private + + def edit_params + { + template: params.require(:template).permit(:path, render_path: []), + url: params.require(:url) + } + end end end diff --git a/app/views/ferbe/shared/_editor.html.erb b/app/views/ferbe/templates/_editor.html.erb similarity index 100% rename from app/views/ferbe/shared/_editor.html.erb rename to app/views/ferbe/templates/_editor.html.erb diff --git a/app/views/ferbe/editor/show.html.erb b/app/views/ferbe/templates/edit.html.erb similarity index 78% rename from app/views/ferbe/editor/show.html.erb rename to app/views/ferbe/templates/edit.html.erb index a03c1d6..76d4f0d 100644 --- a/app/views/ferbe/editor/show.html.erb +++ b/app/views/ferbe/templates/edit.html.erb @@ -6,6 +6,6 @@ use_local_editor: Ferbe.configuration.use_local_editor, turbo_permanent: true } do %> - <%= render partial: "ferbe/shared/editor", locals: { template: @template } %> + <%= render partial: "editor", locals: { template: @template } %> <% end %>
diff --git a/app/views/ferbe/templates/edit.turbo_stream.erb b/app/views/ferbe/templates/edit.turbo_stream.erb index b7e7312..f966e03 100644 --- a/app/views/ferbe/templates/edit.turbo_stream.erb +++ b/app/views/ferbe/templates/edit.turbo_stream.erb @@ -1,3 +1,4 @@ <%= turbo_stream.update "ferbe-editor" do %> - <%= render partial: "ferbe/shared/editor", locals: { template: @template } %> + <%= render partial: "editor", locals: { template: @template } %> <% end %> + diff --git a/config/routes.rb b/config/routes.rb index 64f1d25..c8c13d2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,4 @@ resource :template, only: [:edit, :update] do post :edit_locally, on: :collection end - - resource :editor, only: :show, controller: :editor end From 16e1eb52092f8664e33503a5b1d0da0242d353e2 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 12:25:34 +0200 Subject: [PATCH 15/22] Combine editor and template controller --- .../ferbe/application_controller.rb | 13 ----------- app/controllers/ferbe/editor_controller.rb | 23 ------------------- app/controllers/ferbe/templates_controller.rb | 12 ++++++++++ .../ferbe/templates/edit.turbo_stream.erb | 1 - .../ferbe/editor_controller_test.rb | 23 ------------------- .../ferbe/templates_controller_test.rb | 18 +++++++++++---- 6 files changed, 25 insertions(+), 65 deletions(-) delete mode 100644 app/controllers/ferbe/editor_controller.rb delete mode 100644 test/controllers/ferbe/editor_controller_test.rb diff --git a/app/controllers/ferbe/application_controller.rb b/app/controllers/ferbe/application_controller.rb index e2905c1..7924741 100644 --- a/app/controllers/ferbe/application_controller.rb +++ b/app/controllers/ferbe/application_controller.rb @@ -1,17 +1,4 @@ module Ferbe class ApplicationController < ::ApplicationController - private - - def valid_path?(path) - expanded_path = File.expand_path path - - File.readable?(expanded_path) && - view_path?(expanded_path) && - expanded_path.ends_with?(".erb") - end - - def view_path?(path) - view_paths.any? { |view_path| path.starts_with?(view_path.to_s) } - end end end diff --git a/app/controllers/ferbe/editor_controller.rb b/app/controllers/ferbe/editor_controller.rb deleted file mode 100644 index 0179b6f..0000000 --- a/app/controllers/ferbe/editor_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Ferbe - class EditorController < ApplicationController - def show - template = editor_params[:template] - return head :bad_request unless valid_path? template[:path] - - @template = { - content: File.read(template[:path]), - path: template[:path], - render_path: template[:render_path] - } - - @url = editor_params[:url] - end - - def editor_params - { - template: params.require(:template).permit(:path, render_path: []), - url: params.require(:url) - } - end - end -end diff --git a/app/controllers/ferbe/templates_controller.rb b/app/controllers/ferbe/templates_controller.rb index b20f1ba..47ea579 100644 --- a/app/controllers/ferbe/templates_controller.rb +++ b/app/controllers/ferbe/templates_controller.rb @@ -43,5 +43,17 @@ def edit_params url: params.require(:url) } end + + def valid_path?(path) + expanded_path = File.expand_path path + + File.readable?(expanded_path) && + view_path?(expanded_path) && + expanded_path.ends_with?(".erb") + end + + def view_path?(path) + view_paths.any? { |view_path| path.starts_with?(view_path.to_s) } + end end end diff --git a/app/views/ferbe/templates/edit.turbo_stream.erb b/app/views/ferbe/templates/edit.turbo_stream.erb index f966e03..47b134a 100644 --- a/app/views/ferbe/templates/edit.turbo_stream.erb +++ b/app/views/ferbe/templates/edit.turbo_stream.erb @@ -1,4 +1,3 @@ <%= turbo_stream.update "ferbe-editor" do %> <%= render partial: "editor", locals: { template: @template } %> <% end %> - diff --git a/test/controllers/ferbe/editor_controller_test.rb b/test/controllers/ferbe/editor_controller_test.rb deleted file mode 100644 index 821bd0d..0000000 --- a/test/controllers/ferbe/editor_controller_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require "test_helper" -require "tempfile" - -module Ferbe - class EditorControllerTest < ActionDispatch::IntegrationTest - include Engine.routes.url_helpers - - test "rejection of editor for invalid template" do - invalid_path = "some/invalid/file.txt" - get editor_url, params: {template: {path: invalid_path, render_path: [invalid_path]}, url: "http://example.com"} - assert_response :bad_request - end - - test "success for valid template" do - Tempfile.create(["test", ".html.erb"], Rails.root.join("app/views")) do |file| - file.write "I am a template!" - - get editor_url, params: {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"} - assert_response :success - end - end - end -end diff --git a/test/controllers/ferbe/templates_controller_test.rb b/test/controllers/ferbe/templates_controller_test.rb index 1a26a86..4f1682c 100644 --- a/test/controllers/ferbe/templates_controller_test.rb +++ b/test/controllers/ferbe/templates_controller_test.rb @@ -45,17 +45,25 @@ class TemplatesControllerTest < ActionDispatch::IntegrationTest end end - test "edit as html returns no content" do + test "edit is available as turbo stream and html" do Tempfile.create(["test", ".html.erb"], Rails.root.join("app/views")) do |file| file.write "I am a template!" - get edit_template_url, params: {template: {path: file.path}} - assert_response :no_content + get edit_template_url, + params: {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"} + assert_response :success + + get edit_template_url, + params: {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"}, + as: :turbo_stream + assert_response :success end end - test "reject editing of invalid template" do - get edit_template_url, params: {template: {path: "some/invalid/file.txt"}} + test "rejection of editor for invalid template" do + invalid_path = "some/invalid/file.txt" + get edit_template_url, + params: {template: {path: invalid_path, render_path: [invalid_path]}, url: "http://example.com"} assert_response :bad_request end end From 5c341f1b24ae2b59caafbedbf6970f3db1702e0c Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 14:54:21 +0200 Subject: [PATCH 16/22] Clean up js --- app/assets/javascripts/ferbe/application.js | 3 --- .../controllers/ferbe_editor_controller.js | 3 ++- app/assets/javascripts/ferbe/host.js | 11 +++++++-- .../javascripts/ferbe/utils/editor_opener.js | 24 ------------------- .../javascripts/ferbe/utils/editor_url.js | 11 +++++++++ config/ferbe_importmap.rb | 4 ++-- 6 files changed, 24 insertions(+), 32 deletions(-) delete mode 100644 app/assets/javascripts/ferbe/utils/editor_opener.js create mode 100644 app/assets/javascripts/ferbe/utils/editor_url.js diff --git a/app/assets/javascripts/ferbe/application.js b/app/assets/javascripts/ferbe/application.js index 424f0df..2bd1387 100644 --- a/app/assets/javascripts/ferbe/application.js +++ b/app/assets/javascripts/ferbe/application.js @@ -1,7 +1,4 @@ -import { registerOpeningListener } from "ferbe/utils/editor_opener"; import { application } from "controllers/application"; import FerbeEditorController from "ferbe/controllers/ferbe_editor_controller"; -registerOpeningListener(); - application.register("ferbe-editor", FerbeEditorController); diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js index b466af7..8e1e738 100644 --- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js +++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js @@ -1,10 +1,11 @@ +import editorUrl from "ferbe/utils/editor_url"; + import { Controller } from "@hotwired/stimulus"; import { CodeJar } from "codejar"; import hljs from "highlight.js/lib/core"; import erb from "highlight.js/lib/languages/erb"; import xml from "highlight.js/lib/languages/xml"; import ruby from "highlight.js/lib/languages/ruby"; -import { editorUrl } from "ferbe/utils/editor_opener"; export default class FerbeEditorController extends Controller { static targets = ["editor", "form", "input"]; diff --git a/app/assets/javascripts/ferbe/host.js b/app/assets/javascripts/ferbe/host.js index 0fe60a0..2f9fd33 100644 --- a/app/assets/javascripts/ferbe/host.js +++ b/app/assets/javascripts/ferbe/host.js @@ -1,5 +1,12 @@ import FerbeTemplate from "ferbe/elements/ferbe_template"; -import { registerOpeningListener } from "ferbe/utils/editor_opener"; +import editorUrl from "ferbe/utils/editor_url"; customElements.define("ferbe-template", FerbeTemplate); -registerOpeningListener(); + +addEventListener("ferbe:open-editor", (event) => { + const editor = document.getElementById("ferbe-editor"); + if (editor) return; + + const template = event.detail + window.location.href = editorUrl(template); +}); diff --git a/app/assets/javascripts/ferbe/utils/editor_opener.js b/app/assets/javascripts/ferbe/utils/editor_opener.js deleted file mode 100644 index caca7a9..0000000 --- a/app/assets/javascripts/ferbe/utils/editor_opener.js +++ /dev/null @@ -1,24 +0,0 @@ -export function registerOpeningListener() { - window.addEventListener("ferbe:open-editor", (event) => { - const editor = document.getElementById("ferbe-editor"); - if (editor) return; - - openEditor(event.detail); - }); -} - -function openEditor(template) { - window.location.href = editorUrl(template); -} - -export function editorUrl(template) { - const params = new URLSearchParams(); - - params.append("url", template.url); - params.append("template[path]", template.filePath); - template.renderPath.forEach((p) => - params.append("template[render_path][]", p), - ); - - return `/ferbe/template/edit?${params.toString()}`; -} diff --git a/app/assets/javascripts/ferbe/utils/editor_url.js b/app/assets/javascripts/ferbe/utils/editor_url.js new file mode 100644 index 0000000..609a052 --- /dev/null +++ b/app/assets/javascripts/ferbe/utils/editor_url.js @@ -0,0 +1,11 @@ +export default function editorUrl(template) { + const params = new URLSearchParams(); + + params.append("url", template.url); + params.append("template[path]", template.filePath); + template.renderPath.forEach((p) => + params.append("template[render_path][]", p), + ); + + return `/ferbe/template/edit?${params.toString()}`; +} diff --git a/config/ferbe_importmap.rb b/config/ferbe_importmap.rb index c86fa67..30e4723 100644 --- a/config/ferbe_importmap.rb +++ b/config/ferbe_importmap.rb @@ -1,7 +1,7 @@ +pin_all_from Ferbe::Engine.root.join("app/assets/javascripts/ferbe"), under: "ferbe" + pin "highlight.js/lib/core", to: "https://ga.jspm.io/npm:highlight.js@11.11.1/es/core.js" pin "highlight.js/lib/languages/erb", to: "https://ga.jspm.io/npm:highlight.js@11.11.1/es/languages/erb.js" pin "highlight.js/lib/languages/xml", to: "https://ga.jspm.io/npm:highlight.js@11.11.1/es/languages/xml.js" pin "highlight.js/lib/languages/ruby", to: "https://ga.jspm.io/npm:highlight.js@11.11.1/es/languages/ruby.js" pin "codejar", to: "https://ga.jspm.io/npm:codejar@4.2.0/dist/codejar.js" - -pin_all_from Ferbe::Engine.root.join("app/assets/javascripts/ferbe"), under: "ferbe" From 577b1136bfc056155476ca22b6f21de63f24df53 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 15:03:27 +0200 Subject: [PATCH 17/22] Fix renderpath links --- app/assets/javascripts/ferbe/host.js | 2 +- app/views/ferbe/templates/_editor.html.erb | 11 ++++++----- app/views/ferbe/templates/edit.html.erb | 2 +- app/views/ferbe/templates/edit.turbo_stream.erb | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/ferbe/host.js b/app/assets/javascripts/ferbe/host.js index 2f9fd33..999115c 100644 --- a/app/assets/javascripts/ferbe/host.js +++ b/app/assets/javascripts/ferbe/host.js @@ -7,6 +7,6 @@ addEventListener("ferbe:open-editor", (event) => { const editor = document.getElementById("ferbe-editor"); if (editor) return; - const template = event.detail + const template = event.detail; window.location.href = editorUrl(template); }); diff --git a/app/views/ferbe/templates/_editor.html.erb b/app/views/ferbe/templates/_editor.html.erb index 16bbb0a..551a7fd 100644 --- a/app/views/ferbe/templates/_editor.html.erb +++ b/app/views/ferbe/templates/_editor.html.erb @@ -1,4 +1,4 @@ -<%# locals: (template:) %> +<%# locals: (url:, template:) %>
@@ -9,10 +9,11 @@
 <% template[:render_path].each_with_index do |path, level| %>
 <%= " " * level %>> <%= link_to(Pathname.new(path).relative_path_from(Rails.root),
-                                edit_template_url(template: {
-                                  path: path,
-                                  render_path: template[:render_path]
-                                }),
+                                edit_template_url(url: ,
+                                                  template: {
+                                                    path: path,
+                                                    render_path: template[:render_path]
+                                                  }),
                                 data: { turbo_stream: true }) %>
 <% end %>
 
diff --git a/app/views/ferbe/templates/edit.html.erb b/app/views/ferbe/templates/edit.html.erb index 76d4f0d..adb1b6a 100644 --- a/app/views/ferbe/templates/edit.html.erb +++ b/app/views/ferbe/templates/edit.html.erb @@ -6,6 +6,6 @@ use_local_editor: Ferbe.configuration.use_local_editor, turbo_permanent: true } do %> - <%= render partial: "editor", locals: { template: @template } %> + <%= render partial: "editor", locals: { url: @url, template: @template } %> <% end %>
diff --git a/app/views/ferbe/templates/edit.turbo_stream.erb b/app/views/ferbe/templates/edit.turbo_stream.erb index 47b134a..7072e79 100644 --- a/app/views/ferbe/templates/edit.turbo_stream.erb +++ b/app/views/ferbe/templates/edit.turbo_stream.erb @@ -1,3 +1,3 @@ <%= turbo_stream.update "ferbe-editor" do %> - <%= render partial: "editor", locals: { template: @template } %> + <%= render partial: "editor", locals: { url: @url, template: @template } %> <% end %> From 335f8a92682f25ddfe9d86fd319fa68f7d8d1030 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 15:27:28 +0200 Subject: [PATCH 18/22] Improve title --- app/views/layouts/ferbe/application.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/ferbe/application.html.erb b/app/views/layouts/ferbe/application.html.erb index 6c16274..6d862db 100644 --- a/app/views/layouts/ferbe/application.html.erb +++ b/app/views/layouts/ferbe/application.html.erb @@ -1,7 +1,7 @@ - Ferbe + Ferbe Editor <%= csrf_meta_tags %> <%= csp_meta_tag %> From 6251ae39667200eba1b63e87db13001c5fc4de9a Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 15:44:42 +0200 Subject: [PATCH 19/22] Remove unused helper method --- lib/ferbe/helper.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/ferbe/helper.rb b/lib/ferbe/helper.rb index 5d8830e..13d788a 100644 --- a/lib/ferbe/helper.rb +++ b/lib/ferbe/helper.rb @@ -12,16 +12,6 @@ def ferbe_javascript_tag javascript_import_module_tag "ferbe/host" end - - def ferbe_editor_tag - return unless Ferbe.configuration.enabled - - tag.div class: "ferbe__editor", id: "ferbe-editor", data: { - modifier_key: Ferbe.configuration.modifier_key, - use_local_editor: Ferbe.configuration.use_local_editor, - turbo_permanent: true - } - end #:nocov: end end From 52227286dedab928fcdd39d0db182f72f7bd19f7 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 16:03:28 +0200 Subject: [PATCH 20/22] Remove outdated steps from readme --- README.md | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 327588c..49299eb 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ config.use_local_editor = false # or true ## Installation > [!NOTE] -> This guide assumes that you use ferbe on a standard rails 8.1 app that already has stimulus and importmap set up. +> This guide assumes that you use ferbe on a standard rails 8.0/8.1 app that already has stimulus and importmap set up. Add this line to your application's Gemfile: @@ -91,35 +91,13 @@ $ rails generate ferbe:install ### Layout -In your layout, add these in the head section: +Now, in your layout, add these in the head section: ```erb <%= ferbe_styles_tag %> <%= ferbe_javascript_tag %> ``` -Add the `ferbe_editor_tag` and wrap everything in an element with the `ferbe__page` class. - -```erb -
-
- <%= yield %> -
- -<%= ferbe_editor_tag %> -
-``` - -### Add the Stimulus controller - -The final step is to add the provided Stimulus controller to your `javascript/controllers/index.js`: - -```js -import FerbeEditorController from "ferbe/controllers/ferbe_editor_controller"; -// ... -application.register("ferbe-editor", FerbeEditorController); -``` - --- Copyright 2026 by Renuo AG From ff25cee51fffc0b185698ac60907f808e7776545 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 16:23:32 +0200 Subject: [PATCH 21/22] Clean up controller test --- test/controllers/ferbe/templates_controller_test.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/controllers/ferbe/templates_controller_test.rb b/test/controllers/ferbe/templates_controller_test.rb index 4f1682c..66a15df 100644 --- a/test/controllers/ferbe/templates_controller_test.rb +++ b/test/controllers/ferbe/templates_controller_test.rb @@ -49,13 +49,12 @@ class TemplatesControllerTest < ActionDispatch::IntegrationTest Tempfile.create(["test", ".html.erb"], Rails.root.join("app/views")) do |file| file.write "I am a template!" - get edit_template_url, - params: {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"} + params = {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"} + + get(edit_template_url, params:) assert_response :success - get edit_template_url, - params: {template: {path: file.path, render_path: [file.path]}, url: "http://example.com"}, - as: :turbo_stream + get(edit_template_url, params:, as: :turbo_stream) assert_response :success end end From d7c02e29bcc72eaa84c9718d7dc6f6bcb9cd3124 Mon Sep 17 00:00:00 2001 From: Tim Landolt Date: Tue, 23 Jun 2026 16:41:48 +0200 Subject: [PATCH 22/22] Us link as close button --- .../ferbe/controllers/ferbe_editor_controller.js | 5 ----- app/assets/stylesheets/ferbe/application.css | 6 ++++++ app/views/ferbe/templates/_editor.html.erb | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js index 8e1e738..517f76d 100644 --- a/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js +++ b/app/assets/javascripts/ferbe/controllers/ferbe_editor_controller.js @@ -45,11 +45,6 @@ export default class FerbeEditorController extends Controller { .catch((err) => console.error("Failed to open editor:", err)); } - close() { - const params = new URLSearchParams(document.location.search); - window.location = params.get("url"); - } - save() { this.formTarget.requestSubmit(); } diff --git a/app/assets/stylesheets/ferbe/application.css b/app/assets/stylesheets/ferbe/application.css index f0228bf..08743f8 100644 --- a/app/assets/stylesheets/ferbe/application.css +++ b/app/assets/stylesheets/ferbe/application.css @@ -54,6 +54,12 @@ body { } } + a.ferbe__button { + box-sizing: border-box; + text-decoration: none; + text-align: center; + } + .ferbe__header { display: flex; align-items: baseline; diff --git a/app/views/ferbe/templates/_editor.html.erb b/app/views/ferbe/templates/_editor.html.erb index 551a7fd..6d268fa 100644 --- a/app/views/ferbe/templates/_editor.html.erb +++ b/app/views/ferbe/templates/_editor.html.erb @@ -3,7 +3,7 @@
- + <%= link_to "❌", url, class: "ferbe__button" %>

<%= Pathname.new(template[:path]).relative_path_from(Rails.root) %>