Skip to content

Instantly share code, notes, and snippets.

@neongreen
Last active Nov 11, 2021
Embed
What would you like to do?
Integrating Tiptap with IHP. See https://windofchange.me/ShowCard?cardId=1d4e2e31-f319-4d01-9798-f775e321fdb1 for latest updates.
// This is used in windofchange.me as of November 2021.
// skypack pinned URLs can be obtained by visiting links like 'https://cdn.skypack.dev/@tiptap/core'.
//
// Note that now I regret pulling Tiptap from Skypack and doing customizations by copying-and-pasting
// code into my own app. The right way to do it is probably to fork Tiptap and make your own build
// (from the TypeScript source) that you would later embed into the IHP app as a single module.
//
// Also note that Tiptap does not provide any UI. If you want a ProseMirror-based editor with a built-in UI,
// consider Remirror: https://remirror.io/.
import { Editor, Extension } from 'https://cdn.skypack.dev/pin/@tiptap/core@v2.0.0-beta.103-1EijC0NTac4wtSFPah5m/mode=imports,min/optimized/@tiptap/core.js';
import StarterKit from 'https://cdn.skypack.dev/pin/@tiptap/starter-kit@v2.0.0-beta.102-HbN0d7mePZcMK0Js0Mvg/mode=imports,min/optimized/@tiptap/starter-kit.js';
import Typography from 'https://cdn.skypack.dev/pin/@tiptap/extension-typography@v2.0.0-beta.14-DZDA2cSqeTwyIh1QoLts/mode=imports,min/optimized/@tiptap/extension-typography.js';
// These modules were modified locally, but you can just use the official versions.
import HardBreak from './tiptap-hard-break.js';
import CodeBlock from './tiptap-code-block.js';
import { TrailingNode } from './tiptap-trailing-node.js';
import Link from './tiptap-link.js';
import { Code } from './tiptap-code.js'
import TurndownService from 'https://cdn.skypack.dev/pin/turndown@v7.1.1-5XhlHlbwN5u9Cwiz1WQT/mode=imports,min/optimized/turndown.js';
import * as commonmark from 'https://cdn.skypack.dev/pin/commonmark@v0.30.0-RYoFWHhKKHdGwKqr6TYw/mode=imports,min/optimized/commonmark.js';
var turndownService = new TurndownService();
var cmarkReader = new commonmark.Parser();
var cmarkWriter = new commonmark.HtmlRenderer({safe: true});
// See https://github.com/digitallyinduced/ihp/issues/1177
$(window).on("error", function(evt) {
var e = evt.originalEvent; // get the javascript event
if (e.message) {
alert("JavaScript error:\n " + e.message + "\n" +
"Line:\n " + e.lineno + "\n" +
"File:\n " + e.filename + "\n\n" +
"Please reload the page!");
} else {
alert("JavaScript error:\n " + e.type + "\n" +
"Element:\n " + (e.srcElement || e.target) + "\n\n" +
"Please reload the page!");
}
});
// We want forms with Tiptap to be submittable on Ctrl-Enter
const SubmitShortcut = Extension.create({
name: 'SubmitShortcut',
addKeyboardShortcuts() {
return {
'Mod-Enter': () => window.submitForm($(this.editor.options.element).closest('form')[0])
}
}
})
// Things that will only be called once.
//
// Note that data-turbolinks-permanent doesn't always work (e.g. it doesn't work on the Headway widget)
function onReady() {
// ...
}
// Things that will be called on load, or Turbolinks reloads
function onReadyOrTurbo() {
const newTiptap = function (content) {
return new Editor({
extensions: [
StarterKit,
HardBreak,
Code,
CodeBlock,
Typography,
Link,
TrailingNode,
SubmitShortcut,
],
editorProps: {
attributes: {
class: 'form-control',
},
},
content: content,
});
}
// Revitalize tiptap editors that were killed by a turbolinks forth/back visit
$('textarea.use-tiptap.tiptap-processed').each(function(_, textarea) {
const editorElement = $(textarea).next().children()[0];
if (!editorElement.editor) {
const editor = newTiptap(editorElement.innerHTML);
$(editorElement).replaceWith(editor.options.element);
}
});
// Create tiptap editors for new textareas
$('textarea.use-tiptap:not(.tiptap-processed)').each(function(_, textarea) {
const editor = newTiptap(cmarkWriter.render(cmarkReader.parse(textarea.value)));
$(textarea).after(editor.options.element);
$(textarea).addClass('tiptap-processed');
$(textarea).hide();
if ($(textarea).attr('autofocus')) {
editor.commands.focus();
}
});
// Make all external links open in a new tab
$('a').each(function() {
var a = new RegExp('/' + window.location.host + '/');
if(!a.test(this.href)) {
$(this).click(function(event) {
event.preventDefault();
event.stopPropagation();
window.open(this.href, '_blank');
});
}
});
};
$(document).ready(function () {
onReady();
onReadyOrTurbo();
});
$(document).on('turbolinks:load', function () {
onReadyOrTurbo();
});
// Register a cunning form submit handler that will turn HTML from rich editors into Markdown before submitting the
// form. Note that IHP doesn't use the submit method and instead uses its own logic for submitting the form.
//
// Depends on editors being preceded by plain textareas that we mirror the content to.
//
// https://github.com/digitallyinduced/ihp/blob/90d16d52eb05f2b8086ab3336035669caafb67de/lib/IHP/static/helpers.js#L163
window.__ihp_submitForm = window.submitForm;
window.submitForm = function(form, possibleClickedButton) {
$(form).find('.ProseMirror').each(function(_, editorElement) {
$(editorElement).parent().prev('textarea')[0].value = turndownService.turndown(editorElement.editor.getHTML());
});
if ($(form)[0].onsubmit) {
$(form)[0].onsubmit();
}
return window.__ihp_submitForm(form, possibleClickedButton);
};
-- Example of a form using tiptap
renderForm :: CardUpdate -> Html
renderForm cardUpdate = formFor cardUpdate [hsx|
{(textareaField #content) {
disableLabel = True,
fieldClass = "use-tiptap"
}
}
{submitButton {label = "Save"}}
|]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment