Add base block editor to storybook and page edit
flag=block_editor This is an initial commit for the block editor work. It adds Editor.js, a storybook example, and implements an editor change for wiki pages to use the new component. test plan: - Access the Block Editor component in Storybook. - Verify it renders and works correctly. - Turn the block editor flag on for an account. - Edit a wiki page - Verify the block editor is shown and works. Change-Id: Icc57e7939b70fa141c2be4dde6cf34784ae995c6 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/331823 Reviewed-by: Jacob DeWar <jacob.dewar@instructure.com> QA-Review: Jacob DeWar <jacob.dewar@instructure.com> Migration-Review: Isaac Moore <isaac.moore@instructure.com> Product-Review: Eric Saupe <eric.saupe@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
This commit is contained in:
parent
f801c44dba
commit
c1c7c78118
|
@ -354,7 +354,9 @@ class WikiPagesApiController < ApplicationController
|
||||||
@wiki = @context.wiki
|
@wiki = @context.wiki
|
||||||
@page = @wiki.build_wiki_page(@current_user, initial_params)
|
@page = @wiki.build_wiki_page(@current_user, initial_params)
|
||||||
if authorized_action(@page, @current_user, :create)
|
if authorized_action(@page, @current_user, :create)
|
||||||
update_params = get_update_params(Set[:title, :body])
|
allowed_fields = Set[:title, :body]
|
||||||
|
allowed_fields << :block_editor_attributes if @context.account.feature_enabled?(:block_editor)
|
||||||
|
update_params = get_update_params(allowed_fields)
|
||||||
assign_todo_date
|
assign_todo_date
|
||||||
if !update_params.is_a?(Symbol) && @page.update(update_params) && process_front_page
|
if !update_params.is_a?(Symbol) && @page.update(update_params) && process_front_page
|
||||||
log_asset_access(@page, "wiki", @wiki, "participate")
|
log_asset_access(@page, "wiki", @wiki, "participate")
|
||||||
|
@ -434,6 +436,7 @@ class WikiPagesApiController < ApplicationController
|
||||||
if @page.new_record?
|
if @page.new_record?
|
||||||
perform_update = true if authorized_action(@page, @current_user, [:create])
|
perform_update = true if authorized_action(@page, @current_user, [:create])
|
||||||
allowed_fields = Set[:title, :body]
|
allowed_fields = Set[:title, :body]
|
||||||
|
allowed_fields << :block_editor_attributes if @context.account.feature_enabled?(:block_editor)
|
||||||
elsif authorized_action(@page, @current_user, [:update, :update_content])
|
elsif authorized_action(@page, @current_user, [:update, :update_content])
|
||||||
perform_update = true
|
perform_update = true
|
||||||
allowed_fields = Set[]
|
allowed_fields = Set[]
|
||||||
|
@ -639,7 +642,9 @@ class WikiPagesApiController < ApplicationController
|
||||||
|
|
||||||
def get_update_params(allowed_fields = Set[])
|
def get_update_params(allowed_fields = Set[])
|
||||||
# normalize parameters
|
# normalize parameters
|
||||||
page_params = params[:wiki_page] ? params[:wiki_page].permit(*%w[title body notify_of_update published front_page editing_roles publish_at]) : {}
|
wiki_page_params = %w[title body notify_of_update published front_page editing_roles publish_at]
|
||||||
|
wiki_page_params += [block_editor_attributes: [:time, :version, { blocks: [:id, :type, { data: strong_anything }] }]] if @context.account.feature_enabled?(:block_editor)
|
||||||
|
page_params = params[:wiki_page] ? params[:wiki_page].permit(*wiki_page_params) : {}
|
||||||
|
|
||||||
if page_params.key?(:published)
|
if page_params.key?(:published)
|
||||||
published_value = page_params.delete(:published)
|
published_value = page_params.delete(:published)
|
||||||
|
@ -665,6 +670,10 @@ class WikiPagesApiController < ApplicationController
|
||||||
end
|
end
|
||||||
change_front_page = !!@set_front_page
|
change_front_page = !!@set_front_page
|
||||||
|
|
||||||
|
if page_params.key?(:block_editor_attributes)
|
||||||
|
page_params[:block_editor_attributes][:root_account_id] = @context.root_account_id
|
||||||
|
end
|
||||||
|
|
||||||
# check user permissions
|
# check user permissions
|
||||||
rejected_fields = Set[]
|
rejected_fields = Set[]
|
||||||
if @wiki.grants_right?(@current_user, session, :update)
|
if @wiki.grants_right?(@current_user, session, :update)
|
||||||
|
@ -683,6 +692,7 @@ class WikiPagesApiController < ApplicationController
|
||||||
|
|
||||||
unless @page.grants_right?(@current_user, session, :update)
|
unless @page.grants_right?(@current_user, session, :update)
|
||||||
allowed_fields << :body
|
allowed_fields << :body
|
||||||
|
allowed_fields << :block_editor_attributes if @context.account.feature_enabled?(:block_editor)
|
||||||
rejected_fields << :title if page_params.include?(:title) && page_params[:title] != @page.title
|
rejected_fields << :title if page_params.include?(:title) && page_params[:title] != @page.title
|
||||||
|
|
||||||
rejected_fields << :front_page if change_front_page && !@wiki.grants_right?(@current_user, session, :update)
|
rejected_fields << :front_page if change_front_page && !@wiki.grants_right?(@current_user, session, :update)
|
||||||
|
|
|
@ -174,7 +174,8 @@ class WikiPagesController < ApplicationController
|
||||||
wiki_page_menu_tools: external_tools_display_hashes(:wiki_page_menu),
|
wiki_page_menu_tools: external_tools_display_hashes(:wiki_page_menu),
|
||||||
wiki_index_menu_tools: external_tools_display_hashes(:wiki_index_menu),
|
wiki_index_menu_tools: external_tools_display_hashes(:wiki_index_menu),
|
||||||
DISPLAY_SHOW_ALL_LINK: tab_enabled?(context.class::TAB_PAGES, no_render: true) && !@k5_details_view,
|
DISPLAY_SHOW_ALL_LINK: tab_enabled?(context.class::TAB_PAGES, no_render: true) && !@k5_details_view,
|
||||||
CAN_SET_TODO_DATE: context.grants_any_right?(@current_user, session, :manage_content, :manage_course_content_edit)
|
CAN_SET_TODO_DATE: context.grants_any_right?(@current_user, session, :manage_content, :manage_course_content_edit),
|
||||||
|
BLOCK_EDITOR: context.account.feature_enabled?(:block_editor)
|
||||||
}
|
}
|
||||||
if Account.site_admin.feature_enabled?(:permanent_page_links)
|
if Account.site_admin.feature_enabled?(:permanent_page_links)
|
||||||
title_availability_path = context.is_a?(Course) ? api_v1_course_page_title_availability_path : api_v1_group_page_title_availability_path
|
title_availability_path = context.is_a?(Course) ? api_v1_course_page_title_availability_path : api_v1_group_page_title_availability_path
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 - present Instructure, Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Canvas.
|
||||||
|
#
|
||||||
|
# Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# Canvas 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 Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along
|
||||||
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
class BlockEditor < ActiveRecord::Base
|
||||||
|
belongs_to :context, polymorphic: [:wiki_page]
|
||||||
|
|
||||||
|
alias_attribute :version, :editor_version
|
||||||
|
end
|
|
@ -59,6 +59,8 @@ class WikiPage < ActiveRecord::Base
|
||||||
has_one :master_content_tag, class_name: "MasterCourses::MasterContentTag", inverse_of: :wiki_page
|
has_one :master_content_tag, class_name: "MasterCourses::MasterContentTag", inverse_of: :wiki_page
|
||||||
has_many :assignment_overrides, dependent: :destroy, inverse_of: :wiki_page
|
has_many :assignment_overrides, dependent: :destroy, inverse_of: :wiki_page
|
||||||
has_many :assignment_override_students, dependent: :destroy
|
has_many :assignment_override_students, dependent: :destroy
|
||||||
|
has_one :block_editor, as: :context, dependent: :destroy
|
||||||
|
accepts_nested_attributes_for :block_editor, allow_destroy: true
|
||||||
acts_as_url :title, sync_url: true
|
acts_as_url :title, sync_url: true
|
||||||
|
|
||||||
validate :validate_front_page_visibility
|
validate :validate_front_page_visibility
|
||||||
|
|
|
@ -240,3 +240,11 @@ observer_appointment_groups:
|
||||||
state: allowed_on
|
state: allowed_on
|
||||||
development:
|
development:
|
||||||
state: allowed_on
|
state: allowed_on
|
||||||
|
|
||||||
|
block_editor:
|
||||||
|
applies_to: Account
|
||||||
|
state: hidden
|
||||||
|
display_name: Block Editor
|
||||||
|
description: |-
|
||||||
|
Enable the new block editor for the rich content editor.
|
||||||
|
beta: true
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 - present Instructure, Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Canvas.
|
||||||
|
#
|
||||||
|
# Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# Canvas 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 Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along
|
||||||
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
class CreateBlockEditors < ActiveRecord::Migration[7.0]
|
||||||
|
tag :predeploy
|
||||||
|
|
||||||
|
def change
|
||||||
|
create_table :block_editors do |t|
|
||||||
|
t.references :root_account, null: false, foreign_key: { to_table: :accounts }, index: false
|
||||||
|
t.references :context, polymorphic: true, null: false, index: true
|
||||||
|
t.bigint :time
|
||||||
|
t.jsonb :blocks, default: [], null: false
|
||||||
|
t.string :editor_version
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 - present Instructure, Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Canvas.
|
||||||
|
#
|
||||||
|
# Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# Canvas 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 Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along
|
||||||
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
class AddReplicaIdentityToBlockEditors < ActiveRecord::Migration[7.0]
|
||||||
|
tag :predeploy
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_replica_identity "BlockEditor", :root_account_id
|
||||||
|
end
|
||||||
|
end
|
|
@ -130,6 +130,43 @@ describe WikiPagesApiController, type: :request do
|
||||||
create_wiki_page(@teacher, { title: "New Page", editing_roles: "public" }, 401)
|
create_wiki_page(@teacher, { title: "New Page", editing_roles: "public" }, 401)
|
||||||
expect(WikiPage.last).to be_nil
|
expect(WikiPage.last).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with the block editor" do
|
||||||
|
context "with the block editor feature flag on" do
|
||||||
|
before do
|
||||||
|
Account.default.enable_feature!(:block_editor)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "succeeds" do
|
||||||
|
block_editor_attributes = {
|
||||||
|
time: Time.now.to_i,
|
||||||
|
blocks: [{ "data" => { "text" => "test" }, "id" => "R0iGYLKhw2", "type" => "paragraph" }],
|
||||||
|
version: "1.0"
|
||||||
|
}
|
||||||
|
create_wiki_page(@teacher, { title: "New Page", block_editor_attributes: })
|
||||||
|
expect(WikiPage.last.title).to eq "New Page"
|
||||||
|
expect(WikiPage.last.block_editor).to be_present
|
||||||
|
expect(WikiPage.last.block_editor.blocks).to eq([{ "data" => { "text" => "test" }, "id" => "R0iGYLKhw2", "type" => "paragraph" }])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with the block editor feature flag off" do
|
||||||
|
before do
|
||||||
|
Account.default.disable_feature!(:block_editor)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "ignores the block_editor_attributes" do
|
||||||
|
block_editor_attributes = {
|
||||||
|
time: Time.now.to_i,
|
||||||
|
blocks: [{ "data" => { "text" => "test" }, "id" => "R0iGYLKhw2", "type" => "paragraph" }],
|
||||||
|
version: "1.0"
|
||||||
|
}
|
||||||
|
create_wiki_page(@teacher, { title: "New Page", block_editor_attributes: })
|
||||||
|
expect(WikiPage.last.title).to eq "New Page"
|
||||||
|
expect(WikiPage.last.block_editor).not_to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the user not having manage_wiki_create permission" do
|
context "with the user not having manage_wiki_create permission" do
|
||||||
|
|
|
@ -45,6 +45,7 @@ const featureBundles: {
|
||||||
available_pronouns_list: () => import('./features/available_pronouns_list/index'),
|
available_pronouns_list: () => import('./features/available_pronouns_list/index'),
|
||||||
blueprint_course_child: () => import('./features/blueprint_course_child/index'),
|
blueprint_course_child: () => import('./features/blueprint_course_child/index'),
|
||||||
blueprint_course_master: () => import('./features/blueprint_course_master/index'),
|
blueprint_course_master: () => import('./features/blueprint_course_master/index'),
|
||||||
|
block_editor: () => import('./features/block_editor/index'),
|
||||||
brand_configs: () => import('./features/brand_configs/index'),
|
brand_configs: () => import('./features/brand_configs/index'),
|
||||||
calendar_appointment_group_edit: () => import('./features/calendar_appointment_group_edit/index'),
|
calendar_appointment_group_edit: () => import('./features/calendar_appointment_group_edit/index'),
|
||||||
calendar: () => import('./features/calendar/index'),
|
calendar: () => import('./features/calendar/index'),
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 - present Instructure, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of Canvas.
|
||||||
|
*
|
||||||
|
* Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* Canvas 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 Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import ready from '@instructure/ready'
|
||||||
|
|
||||||
|
import {BlockEditor} from '@canvas/block-editor'
|
||||||
|
|
||||||
|
ready(() => {
|
||||||
|
ReactDOM.render(<BlockEditor />, document.getElementById('block_editor'))
|
||||||
|
})
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "@canvas-features/block_editor",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"owner": "LF"
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "@canvas/block-editor",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "neme",
|
||||||
|
"main": "./react/index.tsx",
|
||||||
|
"dependencies": {
|
||||||
|
"@editorjs/editorjs": "^2.28.2",
|
||||||
|
"@editorjs/header": "^2.7.0",
|
||||||
|
"@editorjs/nested-list": "^1.3.0",
|
||||||
|
"@editorjs/paragraph": "^2.11.3",
|
||||||
|
"@editorjs/quote": "^2.5.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 - present Instructure, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of Canvas.
|
||||||
|
*
|
||||||
|
* Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* Canvas 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 Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import BlockEditor from './BlockEditor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Examples/Shared/BlockEditor',
|
||||||
|
component: BlockEditor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Template = args => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BlockEditor {...args} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultEditor = Template.bind({})
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 - present Instructure, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of Canvas.
|
||||||
|
*
|
||||||
|
* Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* Canvas 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 Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, {useRef} from 'react'
|
||||||
|
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||||
|
import EditorJS from '@editorjs/editorjs'
|
||||||
|
import Header from '@editorjs/header'
|
||||||
|
import NestedList from '@editorjs/nested-list'
|
||||||
|
import Paragraph from '@editorjs/paragraph'
|
||||||
|
import Quote from '@editorjs/quote'
|
||||||
|
|
||||||
|
import {View} from '@instructure/ui-view'
|
||||||
|
|
||||||
|
const I18n = useI18nScope('block-editor')
|
||||||
|
|
||||||
|
export default function BlockEditor() {
|
||||||
|
const editor = useRef<EditorJS | null>(null)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
editor.current = new EditorJS({
|
||||||
|
holder: 'canvas-block-editor',
|
||||||
|
tools: {
|
||||||
|
header: {
|
||||||
|
class: Header,
|
||||||
|
inlineToolbar: true,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
class: NestedList,
|
||||||
|
inlineToolbar: true,
|
||||||
|
config: {
|
||||||
|
defaultStyle: 'unordered',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
class: Paragraph,
|
||||||
|
inlineToolbar: false,
|
||||||
|
},
|
||||||
|
quote: {
|
||||||
|
class: Quote,
|
||||||
|
config: {
|
||||||
|
quotePlaceholder: 'Enter your quote here',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultBlock: 'paragraph',
|
||||||
|
placeholder: I18n.t('Press tab for more options'),
|
||||||
|
})
|
||||||
|
window.block_editor = editor.current
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
as="span"
|
||||||
|
display="inline-block"
|
||||||
|
width="100%"
|
||||||
|
maxWidth="100%"
|
||||||
|
margin="small"
|
||||||
|
padding="small"
|
||||||
|
background="primary"
|
||||||
|
shadow="above"
|
||||||
|
borderRadius="large large none none"
|
||||||
|
>
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
.ce-block__content {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
.ce-toolbar__content {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
<div id="canvas-block-editor" data-testid="canvas-block-editor" />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 - present Instructure, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of Canvas.
|
||||||
|
*
|
||||||
|
* Canvas is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3 of the License.
|
||||||
|
*
|
||||||
|
* Canvas 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 Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import BlockEditor from './BlockEditor'
|
||||||
|
|
||||||
|
export {BlockEditor}
|
||||||
|
|
||||||
|
export default function renderBlockEditorApp(_, elt) {
|
||||||
|
ReactDOM.render(<BlockEditor />, elt)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import $ from 'jquery'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import RichContentEditor from '@canvas/rce/RichContentEditor'
|
import RichContentEditor from '@canvas/rce/RichContentEditor'
|
||||||
|
import {BlockEditor} from '@canvas/block-editor'
|
||||||
import template from '../../jst/WikiPageEdit.handlebars'
|
import template from '../../jst/WikiPageEdit.handlebars'
|
||||||
import ValidatedFormView from '@canvas/forms/backbone/views/ValidatedFormView'
|
import ValidatedFormView from '@canvas/forms/backbone/views/ValidatedFormView'
|
||||||
import WikiPageDeleteDialog from './WikiPageDeleteDialog'
|
import WikiPageDeleteDialog from './WikiPageDeleteDialog'
|
||||||
|
@ -185,7 +186,11 @@ export default class WikiPageEditView extends ValidatedFormView {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.ENV.BLOCK_EDITOR) {
|
||||||
|
ReactDOM.render(<BlockEditor />, document.getElementById('block_editor'))
|
||||||
|
} else {
|
||||||
RichContentEditor.loadNewEditor(this.$wikiPageBody, {focus: true, manageParent: true})
|
RichContentEditor.loadNewEditor(this.$wikiPageBody, {focus: true, manageParent: true})
|
||||||
|
}
|
||||||
|
|
||||||
this.checkUnsavedOnLeave = true
|
this.checkUnsavedOnLeave = true
|
||||||
$(window).on('beforeunload', this.onUnload.bind(this))
|
$(window).on('beforeunload', this.onUnload.bind(this))
|
||||||
|
@ -291,7 +296,7 @@ export default class WikiPageEditView extends ValidatedFormView {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(event) {
|
async submit(event) {
|
||||||
this.checkUnsavedOnLeave = false
|
this.checkUnsavedOnLeave = false
|
||||||
if (this.reloadPending) {
|
if (this.reloadPending) {
|
||||||
if (
|
if (
|
||||||
|
@ -309,6 +314,13 @@ export default class WikiPageEditView extends ValidatedFormView {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (window.block_editor) {
|
||||||
|
let blockEditorData
|
||||||
|
await window.block_editor.save().then((outputData) => {
|
||||||
|
blockEditorData = outputData
|
||||||
|
})
|
||||||
|
this.blockEditorData = blockEditorData
|
||||||
|
}
|
||||||
|
|
||||||
if (this.reloadView != null) {
|
if (this.reloadView != null) {
|
||||||
this.reloadView.stopPolling()
|
this.reloadView.stopPolling()
|
||||||
|
@ -349,7 +361,9 @@ export default class WikiPageEditView extends ValidatedFormView {
|
||||||
if (page_data.publish_at) {
|
if (page_data.publish_at) {
|
||||||
page_data.publish_at = $.unfudgeDateForProfileTimezone(page_data.publish_at)
|
page_data.publish_at = $.unfudgeDateForProfileTimezone(page_data.publish_at)
|
||||||
}
|
}
|
||||||
|
if (this.blockEditorData) {
|
||||||
|
page_data.block_editor_attributes = this.blockEditorData
|
||||||
|
}
|
||||||
if (this.shouldPublish) page_data.published = true
|
if (this.shouldPublish) page_data.published = true
|
||||||
return page_data
|
return page_data
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,12 @@
|
||||||
{{{body}}}
|
{{{body}}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<label for="wiki_page_body" class="hidden-readable" aria-hidden="true">{{#t}}Raw HTML Editor{{/t}}</label>
|
<label for="wiki_page_body" class="hidden-readable" aria-hidden="true">{{#t}}Raw HTML Editor{{/t}}</label>
|
||||||
|
{{#if ENV.BLOCK_EDITOR}}
|
||||||
|
<div id="block_editor"></div>
|
||||||
|
{{else}}
|
||||||
<textarea id="wiki_page_body" rows="20" cols="40" name="body" class="body" aria-hidden="true"{{#unless PAGE_RIGHTS.update}} autofocus{{/unless}}>{{convertApiUserContent body forEditing=1}}</textarea>
|
<textarea id="wiki_page_body" rows="20" cols="40" name="body" class="body" aria-hidden="true"{{#unless PAGE_RIGHTS.update}} autofocus{{/unless}}>{{convertApiUserContent body forEditing=1}}</textarea>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if CAN.EDIT_ROLES}}
|
{{#if CAN.EDIT_ROLES}}
|
||||||
<div class="responsive-control-group options">
|
<div class="responsive-control-group options">
|
||||||
|
|
48
yarn.lock
48
yarn.lock
|
@ -1307,6 +1307,21 @@
|
||||||
exec-sh "^0.3.2"
|
exec-sh "^0.3.2"
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
|
|
||||||
|
"@codexteam/icons@^0.0.2":
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.2.tgz#9183996a38b75a93506890373a015e3a2a369264"
|
||||||
|
integrity sha512-KdeKj3TwaTHqM3IXd5YjeJP39PBUZTb+dtHjGlf5+b0VgsxYD4qzsZkb11lzopZbAuDsHaZJmAYQ8LFligIT6Q==
|
||||||
|
|
||||||
|
"@codexteam/icons@^0.0.4":
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.4.tgz#8b72dcd3f3a1b0d880bdceb2abebd74b46d3ae13"
|
||||||
|
integrity sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q==
|
||||||
|
|
||||||
|
"@codexteam/icons@^0.0.5":
|
||||||
|
version "0.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.5.tgz#d17f39b6a0497c6439f57dd42711817a3dd3679c"
|
||||||
|
integrity sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA==
|
||||||
|
|
||||||
"@colors/colors@1.5.0":
|
"@colors/colors@1.5.0":
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||||
|
@ -1324,6 +1339,39 @@
|
||||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
||||||
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
||||||
|
|
||||||
|
"@editorjs/editorjs@^2.28.2":
|
||||||
|
version "2.29.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@editorjs/editorjs/-/editorjs-2.29.0.tgz#1c9846af19b2afab62a6bb3a815641721c0587f1"
|
||||||
|
integrity sha512-w2BVboSHokMBd/cAOZn0UU328o3gSZ8XUvFPA2e9+ciIGKILiRSPB70kkNdmhHkuNS3q2px+vdaIFaywBl7wGA==
|
||||||
|
|
||||||
|
"@editorjs/header@^2.7.0":
|
||||||
|
version "2.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@editorjs/header/-/header-2.8.1.tgz#ba16f43aaf461aa920c3594bdf0d854c4b5119b9"
|
||||||
|
integrity sha512-y0HVXRP7m2W617CWo3fsb5HhXmSLaRpb9GzFx0Vkp/HEm9Dz5YO1s8tC7R8JD3MskwoYh7V0hRFQt39io/r6hA==
|
||||||
|
dependencies:
|
||||||
|
"@codexteam/icons" "^0.0.5"
|
||||||
|
|
||||||
|
"@editorjs/nested-list@^1.3.0":
|
||||||
|
version "1.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@editorjs/nested-list/-/nested-list-1.4.2.tgz#2b47b9c3ee1ce11dec02eae0b176bd4107360847"
|
||||||
|
integrity sha512-qb1dAoJ+bihqmlR3822TC2GuIxEjTCLTZsZVWNces3uJIZ+W4019G3IJKBt/MOOgz4Evzad/RvUEKwPCPe6YOQ==
|
||||||
|
dependencies:
|
||||||
|
"@codexteam/icons" "^0.0.2"
|
||||||
|
|
||||||
|
"@editorjs/paragraph@^2.11.3":
|
||||||
|
version "2.11.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@editorjs/paragraph/-/paragraph-2.11.3.tgz#fb438de863179739f18de7d8851671a0d8447923"
|
||||||
|
integrity sha512-ON72lhvhgWzPrq4VXpHUeut9bsFeJgVATDeL850FVToOwYHKvdsNpfu0VgxEodhkXgzU/IGl4FzdqC2wd3AJUQ==
|
||||||
|
dependencies:
|
||||||
|
"@codexteam/icons" "^0.0.4"
|
||||||
|
|
||||||
|
"@editorjs/quote@^2.5.0":
|
||||||
|
version "2.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@editorjs/quote/-/quote-2.6.0.tgz#5ff02b5b3cac1fcea4157c31ac975e3acb3906a8"
|
||||||
|
integrity sha512-8DiCMBT4n4UDV5bgzvfRH26HmL6YWddGC4+twvjhR+PzX0gwrnY8nFifvro79EeSqxwRtFjjlGnu5I0VTfw5aQ==
|
||||||
|
dependencies:
|
||||||
|
"@codexteam/icons" "^0.0.5"
|
||||||
|
|
||||||
"@emotion/babel-plugin@^11.11.0":
|
"@emotion/babel-plugin@^11.11.0":
|
||||||
version "11.11.0"
|
version "11.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
|
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
|
||||||
|
|
Loading…
Reference in New Issue