Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add docs for autosave #202

Merged
merged 2 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Add Additional Attributes onto the Editor
permalink: /add-additional-attributes-onto-the-editor/
permalink: /how-tos/add-additional-attributes-onto-the-editor/
---

Sometimes you may want to add additional attributes directly onto the `contenteditable` of RhinoEditor.
Expand Down
122 changes: 122 additions & 0 deletions docs/src/_documentation/how_tos/16-implementing-autosave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
title: Implementing Autosave
permalink: /how-tos/implementing-autosave/
---

Big thank you to Seth Addison for the initial code for this.

Implementing autosave can be done a number of ways.

```js
// controllers/rhino_autosave_controller.js
import { Controller } from "@hotwired/stimulus";

// https://dev.to/jeetvora331/throttling-in-javascript-easiest-explanation-1081
function throttle(mainFunction, delay) {
let timerFlag = null; // Variable to keep track of the timer

// Returning a throttled version
return (...args) => {
if (timerFlag === null) { // If there is no timer currently running
mainFunction(...args); // Execute the main function
timerFlag = setTimeout(() => { // Set a timer to clear the timerFlag after the specified delay
timerFlag = null; // Clear the timerFlag to allow the main function to be executed again
}, delay);
}
};
}
export default class RhinoAutosave extends Controller {
initialize() {
// Throttle to avoid too many requests in a short time. This will save the editor at most 1 time every 300ms. Feel free to tune this number to better handle your workloads.
this.handleEditorChange = throttle(this.handleEditorChange.bind(this), 300)
}

connect() {
// "rhino-change" fires everytime something in the editor changes.
this.element.addEventListener(
"rhino-change",
this.handleEditorChange
); // Listen for rhino-change
}

disconnect() {
this.element.removeEventListener(
"rhino-change",
this.handleEditorChange
);
}

handleEditorChange() {
// Don't need to await. We're not relying on the response.
this.submitForm()
}


async submitForm() {
const form = this.element.closest("form");
const formData = new FormData(form);

try {
const response = await fetch(form.getAttribute("action"), {
// Its technically a "PATCH", but Rails will sort it out for us by using the `form_with`
method: "POST",
body: formData,
headers: {
Accept: "application/json",
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content,
},
});

if (!response.ok) {
const json = await response.json()
const errors = json["errors"]
// Decide how you want to use errors here, if at all.
console.error("Auto-save failed", errors);
} else {
console.log("Auto-save successful");
}
} catch (error) {
// This is usually a network error like a user losing connection, and not a 404 / 500 / etc.
console.error("Error in auto-save", error);
}
}
}
```

This assume you have a DOM like the following:

```erb
<%%= form_with model: @model do %>
<rhino-editor data-controller="rhino-autosave"></rhino-editor>
<%% end %>
```

You'll also need your controller to respond to JSON, something like the following:

```rb
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
if @post.update(post_params)
respond_to do |fmt|
fmt.html { redirect_to @post }
fmt.json { render json: {}, status: 200 }
end
else
respond_to do |fmt|
fmt.html { render :edit, status: 422 }
fmt.json { render json: { errors: @post.errors.full_messages }, status: 422 } }
end
end
end

private

def post_params
# We assume your model is something like `has_rich_text :content`
params.require(:post).permit(:content)
end
end
```

With the above, "autosave" should start working for you! (And if it doesn't please open an issue and I'd be happy to take a look!)
4 changes: 3 additions & 1 deletion tests/rails/test/system/attachment_galleries_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def check
end

test "Should not allow to insert multiple attachments in the gallery are inserted at the same time" do
skip("For some silly reason, this test only fails in GH Actions") if ENV["CI"] == "true"
page.get_by_role('link', name: /New Post/i).click

wait_for_network_idle
Expand Down Expand Up @@ -107,6 +106,9 @@ def check

check

# For some silly reason, this test only fails in GH Actions
return if ENV["CI"] == "true"

# Save the attachment, make sure we render properly.
page.get_by_role('button', name: /Create Post/i).click

Expand Down