Use BlockNote in open project

This commit is contained in:
Bruno Pagno
2025-05-23 13:50:12 +02:00
parent 7cbfc92d37
commit ab2013677c
11 changed files with 4854 additions and 71 deletions
+43
View File
@@ -0,0 +1,43 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
render(
Primer::BaseComponent.new(
tag: :div,
border: true, border_radius: 2, mt: 1, mb: 1,
data: {
controller: "block-note",
block_note_target: "blockNoteEditor",
block_note_input_name_value: input_name,
block_note_input_text_value: value
}
)
)
%>
+39
View File
@@ -0,0 +1,39 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class BlockNoteEditor < ApplicationComponent
attr_reader :input_name, :value
def initialize(input_name, value, **)
super(**)
@input_name = input_name
@value = value
end
end
+3
View File
@@ -46,3 +46,6 @@ OpenProject::FeatureDecisions.add :built_in_oauth_applications,
OpenProject::FeatureDecisions.add :calculated_value_project_attribute,
description: "Allows the use of calculated values as a project attribute."
OpenProject::FeatureDecisions.add :block_note_editor,
description: "Enables the block note editor for rich text fields where available."
+1
View File
@@ -57,6 +57,7 @@
"bundleName": "backlogs"
},
"src/styles.scss",
"node_modules/@blocknote/mantine/dist/style.css",
"node_modules/codemirror/lib/codemirror.css",
"node_modules/jquery-ui/themes/base/core.css",
"node_modules/jquery-ui/themes/base/datepicker.css",
+4587 -58
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -26,6 +26,8 @@
"@types/mime": "^2.0.3",
"@types/mousetrap": "^1.6.3",
"@types/pako": "^1.0.1",
"@types/react": "^19.1.5",
"@types/react-dom": "^19.1.5",
"@types/resize-observer-browser": "^0.1.4",
"@types/urijs": "^1.19.6",
"@types/uuid": "^8.3.4",
@@ -52,8 +54,6 @@
"karma-jasmine": "~3.3.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-spec-reporter": "^0.0.32",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"source-map-explorer": "^2.5.2",
"theo": "^8.1.5",
"ts-node": "~8.3.0",
@@ -77,6 +77,9 @@
"@appsignal/javascript": "^1.3.28",
"@appsignal/plugin-breadcrumbs-console": "^1.1.29",
"@appsignal/plugin-breadcrumbs-network": "^1.1.22",
"@blocknote/core": "^0.31.0",
"@blocknote/mantine": "^0.31.0",
"@blocknote/react": "^0.31.0",
"@braintree/sanitize-url": "^7.1.1",
"@datorama/akita": "^8.0.1",
"@floating-ui/dom": "^1.2.1",
@@ -149,8 +152,11 @@
"ng2-dragula": "^5.1.0",
"ngx-cookie-service": "^14.0.0",
"observable-array": "0.0.4",
"op-blocknote-extensions": "github:opf/op-blocknote-extensions",
"pako": "^2.0.3",
"qr-creator": "^1.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"screenfull": "^4.2.1",
@@ -0,0 +1,94 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) 2023 the OpenProject GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 3.
*
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
* Copyright (C) 2006-2013 Jean-Philippe Lang
* Copyright (C) 2010-2013 the ChiliProject Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* See COPYRIGHT and LICENSE files for more details.
* ++
*/
import { BlockNoteSchema, defaultBlockSpecs, filterSuggestionItems } from "@blocknote/core";
import { BlockNoteView } from "@blocknote/mantine";
import { getDefaultReactSlashMenuItems, SuggestionMenuController, useCreateBlockNote } from "@blocknote/react";
import { dummyBlockSpec, getDefaultOpenProjectSlashMenuItems, openProjectWorkPackageBlockSpec } from "op-blocknote-extensions";
import { useEffect, useState } from "react";
interface OpBlockNoteContainerProps {
inputName?: string;
inputText?: string;
}
const schema = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
openProjectWorkPackage: openProjectWorkPackageBlockSpec,
dummy: dummyBlockSpec,
},
});
export default function OpBlockNoteContainer({ inputName, inputText }: OpBlockNoteContainerProps) {
const [isLoading, setIsLoading] = useState(true);
const [editorContent, setEditorContent] = useState(inputText || "");
const editor = useCreateBlockNote({ schema });
type EditorType = typeof editor;
const getCustomSlashMenuItems = (editor: EditorType) => {
return [
...getDefaultReactSlashMenuItems(editor),
...getDefaultOpenProjectSlashMenuItems(editor),
];
};
useEffect(() => {
async function loadInitialContent() {
const blocks = await editor.tryParseMarkdownToBlocks(inputText || "");
editor.replaceBlocks(editor.document, blocks);
setEditorContent(await editor.blocksToMarkdownLossy());
setIsLoading(false);
}
loadInitialContent();
}, [editor]);
return (
<>
<input type="hidden" name={inputName} value={editorContent} />
{isLoading ? <div>Loading...</div>
:
<BlockNoteView
editor={editor}
onChange={async (editor) => {
const content = await editor.blocksToMarkdownLossy();
setEditorContent(content);
}}
>
<SuggestionMenuController
triggerCharacter="/"
getItems={async (query: string) => filterSuggestionItems(getCustomSlashMenuItems(editor), query)}
/>
</BlockNoteView>
}
</>
);
}
@@ -0,0 +1,58 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) 2023 the OpenProject GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 3.
*
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
* Copyright (C) 2006-2013 Jean-Philippe Lang
* Copyright (C) 2010-2013 the ChiliProject Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* See COPYRIGHT and LICENSE files for more details.
* ++
*/
import { Controller } from '@hotwired/stimulus';
import React from 'react';
import { createRoot } from 'react-dom/client';
import OpBlockNoteContainer from 'react/OpBlockNoteContainer';
export default class extends Controller {
static targets = ['blockNoteEditor'];
static values = {
inputName: String,
inputText: String,
};
declare readonly blockNoteEditorTarget:HTMLElement;
declare readonly inputNameValue:string;
declare readonly inputTextValue:string;
connect() {
const root = createRoot(this.blockNoteEditorTarget);
root.render(this.BlockNoteReactContainer());
}
BlockNoteReactContainer() {
return React.createElement(OpBlockNoteContainer, {
inputName: this.inputNameValue,
inputText: this.inputTextValue,
});
}
}
+2 -1
View File
@@ -16,9 +16,10 @@
"include": [
"src/**/*.ts",
"stimulus/**/*.ts",
"**/*.d.ts",
"**/*.d.ts"
],
"exclude": [
"**/*.stories.*"
]
}
+5
View File
@@ -24,6 +24,11 @@
"node_modules/@types",
"src/types"
],
"types": [
"node",
"react"
],
"jsx": "react-jsx",
"lib": [
"es2018",
"dom"
@@ -38,13 +38,17 @@ See COPYRIGHT and LICENSE files for more details.
<div class="form--field -required">
<%= f.text_field :title, required: true, container_class: "-wide" %>
</div>
<div class="form--field -visible-overflow">
<%= f.text_area :description,
container_class: "-xxwide",
label_options: {
class: "-top"
},
with_text_formatting: true,
resource: api_v3_document_resource(f.object),
preview_context: preview_context(f.object.project) %>
</div>
<% if OpenProject::FeatureDecisions.block_note_editor_active? %>
<%= render BlockNoteEditor.new("#{f.object_name}[description]", f.object.description) %>
<% else %>
<div class="form--field -visible-overflow">
<%= f.text_area :description,
container_class: "-xxwide",
label_options: {
class: "-top"
},
with_text_formatting: true,
resource: api_v3_document_resource(f.object),
preview_context: preview_context(f.object.project) %>
</div>
<% end %>