update the block editor data format

the legacy block editor code assumed the json format for
the block_editors.blocks column was
   blocks: [data: {editor data}, id, timestamp]
that was an artifact of the data structure emitted by editor.js.
Now we just want to put the craft.js editor's json output directly
into the blocks column.

The data migration also changes the editor_version value
from '1' to '0.1' because '1' doesn't seem right

this commit also introduces data transformations to update the data
to the latest version on the fly

closes RCX-2369
flag=block_editor

test plan:
  - before checking this branch out, have a block editor page
  - after checking this branch out
    - run bundle exec rake db:migrate RAILS_ENV=development
    - run psql canvas_development
    - select id, editor_version from block_editors;
    > expect your pages to have version 0.1
  - edit the old v0.1 page
  > expect it to work
  - save
  > expect it to have v0.2 in the block_editors table
  - from the pages index page, choose Duplicate from a
    page's kabob menu
  > expect the correct type of page (rce or block) icon
    to be displyed
  > expect to be able to view the copy of the page
  > expect to be able to edit the copy of the page
  - run:
    bundle exec rake db:migrate:down VERSION=20220228162704
  - select id, editor_version from block_editors;
  > expect the old v0.1 pages to revert to '1'
  > expect the new v0.2 pages to remain '0.2'

Change-Id: I592bffc11018ab3c4f7d7cc4752202088d5700c5
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/357583
Migration-Review: Cody Cutrer <cody@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Eric Saupe <eric.saupe@instructure.com>
QA-Review: Eric Saupe <eric.saupe@instructure.com>
Product-Review: Ed Schiebel <eschiebel@instructure.com>
This commit is contained in:
Ed Schiebel 2024-09-16 16:00:17 -06:00
parent 9dc23fe15e
commit 388d995568
19 changed files with 286 additions and 77 deletions

View File

@ -643,7 +643,7 @@ class WikiPagesApiController < ApplicationController
def get_update_params(allowed_fields = Set[])
# normalize parameters
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: [data: strong_anything] }]] if @context.account.feature_enabled?(:block_editor)
wiki_page_params += [block_editor_attributes: [:time, :version, { blocks: 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)

View File

@ -589,6 +589,7 @@ class WikiPage < ActiveRecord::Base
copy_title: nil
}
opts_with_default = default_opts.merge(opts)
result = WikiPage.new({
title: opts_with_default[:copy_title] || get_copy_title(self, t("Copy"), self.title),
wiki_id: self.wiki_id,
@ -599,8 +600,14 @@ class WikiPage < ActiveRecord::Base
user_id:,
protected_editing:,
editing_roles:,
todo_date:
todo_date:,
})
if block_editor
block_editor_attributes = { version: block_editor.version, blocks: block_editor.blocks }
result.block_editor_attributes = block_editor_attributes
end
if assignment && opts_with_default[:duplicate_assignment]
result.assignment = assignment.duplicate({
duplicate_wiki_page: false,

View File

@ -0,0 +1,31 @@
# 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 FixBlockEditorVersion1 < ActiveRecord::Migration[7.1]
tag :postdeploy
def up
change_column_default :block_editors, :blocks, from: [], to: nil
DataFixup::FixBlockEditorVersion1.delay_if_production(
priority: Delayed::LOW_PRIORITY,
n_strand: "long_datafixups"
).run
end
end

View File

@ -0,0 +1,24 @@
# 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/>.
module DataFixup::FixBlockEditorVersion1
def self.run
BlockEditor.where(editor_version: "1").in_batches(strategy: :pluck_ids).update_all(editor_version: "0.1")
end
end

View File

@ -31,11 +31,7 @@ describe "Pages API", type: :request do
{
time: Time.now.to_i,
version: "1",
blocks: [
{
data: '{"ROOT":{"type": ...}'
}
]
blocks: '{"ROOT":{"type": ...}'
}
end
@ -46,11 +42,7 @@ describe "Pages API", type: :request do
@block_page = @course.wiki_pages.create!(title: "Block editor page", block_editor_attributes: {
time: Time.now.to_i,
version: "1",
blocks: [
{
data: '{"ROOT":{"type": ...}'
}
]
blocks: '{"ROOT":{"type": ...}'
})
end
@ -77,12 +69,11 @@ describe "Pages API", type: :request do
format: "json",
course_id: @course.to_param,
include: ["body"])
expect(json[0].keys).not_to include("body")
expect(json[0].keys).to include("block_editor_attributes")
returned_attributes = json[0]["block_editor_attributes"]
expect(returned_attributes["version"]).to eq(block_page_data[:version])
expect(returned_attributes["blocks"][0]["data"]).to eq(block_page_data[:blocks][0][:data])
expect(returned_attributes["blocks"]).to eq(block_page_data[:blocks])
end
end
@ -99,7 +90,7 @@ describe "Pages API", type: :request do
expect(json["editor"]).to eq("block_editor")
returned_attributes = json["block_editor_attributes"]
expect(returned_attributes["version"]).to eq(block_page_data[:version])
expect(returned_attributes["blocks"][0]["data"]).to eq(block_page_data[:blocks][0][:data])
expect(returned_attributes["blocks"]).to eq(block_page_data[:blocks])
end
it "retrieves rce editor page content and attributes" do
@ -127,7 +118,7 @@ describe "Pages API", type: :request do
expect(page.title).to eq "New Block Page!"
expect(page.url).to eq "new-block-page"
expect(page.body).to be_nil
expect(page.block_editor["blocks"][0]["data"]).to eq block_page_data[:blocks][0][:data]
expect(page.block_editor["blocks"]).to eq block_page_data[:blocks]
expect(page.block_editor["editor_version"]).to eq block_page_data[:version]
end
@ -143,7 +134,7 @@ describe "Pages API", type: :request do
expect(page.is_front_page?).to be_truthy
expect(page.title).to eq "New Block Front Page!"
expect(page.body).to be_nil
expect(page.block_editor["blocks"][0]["data"]).to eq block_page_data[:blocks][0][:data]
expect(page.block_editor["blocks"]).to eq block_page_data[:blocks]
expect(page.block_editor["editor_version"]).to eq block_page_data[:version]
end
end
@ -153,11 +144,7 @@ describe "Pages API", type: :request do
new_block_data = {
time: Time.now.to_i,
version: "1",
blocks: [
{
data: '{"ROOT":{"a_different_type": ...}'
}
]
blocks: '{"ROOT":{"a_different_type": ...}'
}
api_call(:put,
"/api/v1/courses/#{@course.id}/pages/#{@block_page.url}",
@ -168,7 +155,7 @@ describe "Pages API", type: :request do
url_or_id: @block_page.url },
{ wiki_page: { block_editor_attributes: new_block_data } })
@block_page.reload
expect(@block_page.block_editor["blocks"][0]["data"]).to eq new_block_data[:blocks][0][:data]
expect(@block_page.block_editor["blocks"]).to eq new_block_data[:blocks]
expect(@block_page.body).to be_nil
end
end

View File

@ -140,13 +140,13 @@ describe WikiPagesApiController, type: :request do
it "succeeds" do
block_editor_attributes = {
time: Time.now.to_i,
blocks: [{ "data" => { "text" => "test" }, "id" => "R0iGYLKhw2", "type" => "paragraph" }],
version: "1.0"
blocks: { "text" => "test", "id" => "R0iGYLKhw2", "type" => "paragraph" },
version: "0.2"
}
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" }])
expect(WikiPage.last.block_editor.blocks).to eq({ "text" => "test", "id" => "R0iGYLKhw2", "type" => "paragraph" })
end
end
@ -158,7 +158,7 @@ describe WikiPagesApiController, type: :request do
it "ignores the block_editor_attributes" do
block_editor_attributes = {
time: Time.now.to_i,
blocks: [{ "data" => { "text" => "test" }, "id" => "R0iGYLKhw2", "type" => "paragraph" }],
blocks: { "text" => "test", "id" => "R0iGYLKhw2", "type" => "paragraph" },
version: "1.0"
}
create_wiki_page(@teacher, { title: "New Page", block_editor_attributes: })

View File

@ -241,11 +241,7 @@ describe "Block Editor", :ignore_js_errors do
block_editor_attributes: {
time: Time.now.to_i,
version: "1",
blocks: [
{
data: "{\"ROOT\":{\"type\":{\"resolvedName\":\"PageBlock\"},\"isCanvas\":true,\"props\":{},\"displayName\":\"Page\",\"custom\":{},\"hidden\":false,\"nodes\":[\"AcfL3KeXTT\"],\"linkedNodes\":{}},\"AcfL3KeXTT\":{\"type\":{\"resolvedName\":\"BlankSection\"},\"isCanvas\":false,\"props\":{},\"displayName\":\"Blank Section\",\"custom\":{\"isSection\":true},\"parent\":\"ROOT\",\"hidden\":false,\"nodes\":[],\"linkedNodes\":{\"blank-section_nosection1\":\"0ZWqBwA2Ou\"}},\"0ZWqBwA2Ou\":{\"type\":{\"resolvedName\":\"NoSections\"},\"isCanvas\":true,\"props\":{\"className\":\"blank-section__inner\",\"placeholderText\":\"Drop a block to add it here\"},\"displayName\":\"NoSections\",\"custom\":{\"noToolbar\":true},\"parent\":\"AcfL3KeXTT\",\"hidden\":false,\"nodes\":[\"lLVSJCBPWm\"],\"linkedNodes\":{}},\"lLVSJCBPWm\":{\"type\":{\"resolvedName\":\"ImageBlock\"},\"isCanvas\":false,\"props\":{\"src\":\"/courses/#{@course.id}/files/#{@image.id}/preview\",\"variant\":\"default\",\"constraint\":\"cover\",\"maintainAspectRatio\":true,\"sizeVariant\":\"pixel\",\"width\":200,\"height\":100},\"displayName\":\"Image\",\"custom\":{\"isResizable\":true},\"parent\":\"0ZWqBwA2Ou\",\"hidden\":false,\"nodes\":[],\"linkedNodes\":{}}}"
}
]
blocks: "{\"ROOT\":{\"type\":{\"resolvedName\":\"PageBlock\"},\"isCanvas\":true,\"props\":{},\"displayName\":\"Page\",\"custom\":{},\"hidden\":false,\"nodes\":[\"AcfL3KeXTT\"],\"linkedNodes\":{}},\"AcfL3KeXTT\":{\"type\":{\"resolvedName\":\"BlankSection\"},\"isCanvas\":false,\"props\":{},\"displayName\":\"Blank Section\",\"custom\":{\"isSection\":true},\"parent\":\"ROOT\",\"hidden\":false,\"nodes\":[],\"linkedNodes\":{\"blank-section_nosection1\":\"0ZWqBwA2Ou\"}},\"0ZWqBwA2Ou\":{\"type\":{\"resolvedName\":\"NoSections\"},\"isCanvas\":true,\"props\":{\"className\":\"blank-section__inner\",\"placeholderText\":\"Drop a block to add it here\"},\"displayName\":\"NoSections\",\"custom\":{\"noToolbar\":true},\"parent\":\"AcfL3KeXTT\",\"hidden\":false,\"nodes\":[\"lLVSJCBPWm\"],\"linkedNodes\":{}},\"lLVSJCBPWm\":{\"type\":{\"resolvedName\":\"ImageBlock\"},\"isCanvas\":false,\"props\":{\"src\":\"/courses/#{@course.id}/files/#{@image.id}/preview\",\"variant\":\"default\",\"constraint\":\"cover\",\"maintainAspectRatio\":true,\"sizeVariant\":\"pixel\",\"width\":200,\"height\":100},\"displayName\":\"Image\",\"custom\":{\"isResizable\":true},\"parent\":\"0ZWqBwA2Ou\",\"hidden\":false,\"nodes\":[],\"linkedNodes\":{}}}"
}
)
end

View File

@ -47,11 +47,7 @@ describe "Block Editor", :ignore_js_errors do
block_editor_attributes: {
time: Time.now.to_i,
version: "1",
blocks: [
{
data: block_page_content
}
]
blocks: block_page_content
}
)
end

View File

@ -34,11 +34,7 @@ module BlockEditorPage
block_editor_attributes: {
time: Time.now.to_i,
version: "1",
blocks: [
{
data: block_page_content
}
]
blocks: block_page_content
}
)
block_page

View File

@ -253,7 +253,7 @@ export default class WikiPageIndexView extends PaginatedCollectionView {
block_editor_attributes: this.createNewPageWithBlockEditor
? {
version: '1',
blocks: [{data: undefined}],
blocks: undefined,
}
: null,
},

View File

@ -195,13 +195,13 @@ export default class WikiPageView extends Backbone.View {
maybeRenderBlockEditorContent() {
if (
this.model.get('editor') === 'block_editor' &&
this.model.get('block_editor_attributes')?.blocks?.[0]?.data
this.model.get('block_editor_attributes')?.blocks
) {
import('@canvas/block-editor')
.then(({renderBlockEditorView}) => {
const container = document.getElementById('block-editor-content')
container.classList.add('block-editor-view')
const content = JSON.parse(this.model.get('block_editor_attributes').blocks[0].data)
const content = this.model.get('block_editor_attributes')
renderBlockEditorView(content, container)
})
.catch(e => {

View File

@ -28,6 +28,12 @@ import {NewPageStepper} from './components/editor/NewPageStepper'
import {RenderNode} from './components/editor/RenderNode'
import {ErrorBoundary} from './components/editor/ErrorBoundary'
import {closeExpandedBlocks} from './utils/cleanupBlocks'
import {
transform,
LATEST_BLOCK_DATA_VERSION,
type BlockEditorDataTypes,
type BlockEditorData,
} from './utils/transformations'
import './style.css'
@ -76,12 +82,11 @@ const DEFAULT_CONTENT = JSON.stringify({
},
})
type BlockEditorProps = {
export type BlockEditorProps = {
enabled?: boolean
enableResizer?: boolean
container: HTMLElement // the element that will shrink when drawers open
version: string
content: string
content: BlockEditorDataTypes
onCancel: () => void
}
@ -89,28 +94,35 @@ export default function BlockEditor({
enabled = true,
enableResizer = true,
container,
version,
content,
onCancel,
}: BlockEditorProps) {
const [json] = useState(content || DEFAULT_CONTENT)
const [data] = useState<BlockEditorData>(() => {
if (content?.blocks) {
return transform(content)
}
return {version: '0.2', blocks: DEFAULT_CONTENT} as BlockEditorData
})
const [toolboxOpen, setToolboxOpen] = useState(false)
const [stepperOpen, setStepperOpen] = useState(!content)
const [stepperOpen, setStepperOpen] = useState(!content?.blocks)
RenderNode.globals.enableResizer = !!enableResizer
useEffect(() => {
if (version !== '1') {
if (data.version !== LATEST_BLOCK_DATA_VERSION) {
// eslint-disable-next-line no-alert
alert(I18n.t('wrong version, mayhem may ensue'))
alert(I18n.t('Unknown block data version "%{v}", mayhem may ensue', {v: data.version}))
}
}, [json, version])
}, [data.version])
const handleNodesChange = useCallback((query: any) => {
// @ts-expect-error
window.block_editor = () => ({
query,
getBlocks: (): string => closeExpandedBlocks(query),
getBlocks: (): BlockEditorData => ({
version: '0.2',
blocks: closeExpandedBlocks(query),
}),
})
}, [])
@ -174,7 +186,7 @@ export default function BlockEditor({
<Topbar onToolboxChange={handleOpenToolbox} toolboxOpen={toolboxOpen} />
</div>
<Flex.Item id="editor-area" shouldGrow={true} role="tree">
<Frame data={json} />
<Frame data={data.blocks} />
</Flex.Item>
</Flex>

View File

@ -16,20 +16,43 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import React, {useEffect, useState} from 'react'
import {Editor, Frame} from '@craftjs/core'
import {blocks} from './components/blocks'
import {
transform,
LATEST_BLOCK_DATA_VERSION,
type BlockEditorDataTypes,
type BlockEditorData,
} from './utils/transformations'
import {useScope as useI18nScope} from '@canvas/i18n'
import './style.css'
const I18n = useI18nScope('block-editor')
type BlockEditorViewProps = {
content: string
content: BlockEditorDataTypes
}
const BlockEditorView = ({content}: BlockEditorViewProps) => {
const [data] = useState<BlockEditorData>(() => {
if (content?.blocks) {
return transform(content)
}
return {version: '0.2', blocks: undefined} as BlockEditorData
})
useEffect(() => {
if (data.version !== LATEST_BLOCK_DATA_VERSION) {
// eslint-disable-next-line no-alert
alert(I18n.t('Unknown block data version "%{v}", mayhem may ensue', {v: data.version}))
}
}, [data.version])
return (
<Editor enabled={false} resolver={blocks}>
<Frame data={content} />
<Frame data={data.blocks} />
</Editor>
)
}

View File

@ -24,12 +24,12 @@ import {
getByLabelText as domGetByLabelText,
} from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
import BlockEditor from '../BlockEditor'
import BlockEditor, {type BlockEditorProps} from '../BlockEditor'
import {blank_page, blank_section_with_text} from './test-content'
const user = userEvent.setup()
function renderEditor(props = {}) {
function renderEditor(props: Partial<BlockEditorProps> = {}) {
const container = document.createElement('div')
container.id = 'drawer-layout-content'
container.scrollTo = () => {}
@ -38,9 +38,8 @@ function renderEditor(props = {}) {
return render(
<BlockEditor
container={container}
version="1"
enableResizer={false} // jsdom doesn't render enough for BlockResizer to work
content={blank_page}
content={{version: '0.2', blocks: blank_page}}
onCancel={() => {}}
{...props}
/>,
@ -62,8 +61,8 @@ describe('BlockEditor', () => {
})
it('warns on content version mismatch', () => {
renderEditor({version: '2'})
expect(window.alert).toHaveBeenCalledWith('wrong version, mayhem may ensue')
renderEditor({content: {version: '2', blocks: blank_page}})
expect(window.alert).toHaveBeenCalledWith('Unknown block data version "2", mayhem may ensue')
})
describe('New page stepper', () => {
@ -111,11 +110,23 @@ describe('BlockEditor', () => {
})
})
describe('data transformations', () => {
it('can edit version 0.1 data', () => {
renderEditor({
content: {
version: '0.1',
blocks: [{data: blank_section_with_text}],
},
})
expect(screen.getByText('this is text.')).toBeInTheDocument()
})
})
describe('Preview', () => {
it('toggles the preview', async () => {
// rebnder a page with a blank section containing a text block
const {getByText} = renderEditor({
content: blank_section_with_text,
content: {version: '0.2', blocks: blank_section_with_text},
})
await user.click(getByText('Preview').closest('button') as HTMLButtonElement)
@ -138,7 +149,7 @@ describe('BlockEditor', () => {
it('adjusts the view size', async () => {
// rebnder a page with a blank section containing a text block
const {getByText} = renderEditor({
content: blank_section_with_text,
content: {version: '0.2', blocks: blank_section_with_text},
})
await user.click(getByText('Preview').closest('button') as HTMLButtonElement)

View File

@ -23,6 +23,7 @@ import {Modal} from '@instructure/ui-modal'
import {RadioInputGroup, RadioInput} from '@instructure/ui-radio-input'
import {View} from '@instructure/ui-view'
import BlockEditorView from '../../BlockEditorView'
import {LATEST_BLOCK_DATA_VERSION} from '../../utils/transformations'
import {useScope as useI18nScope} from '@canvas/i18n'
@ -100,7 +101,9 @@ const PreviewModal = ({open, onDismiss}: PreviewModalProps) => {
padding="0"
margin="0 auto"
>
<BlockEditorView content={query.serialize()} />
<BlockEditorView
content={{version: LATEST_BLOCK_DATA_VERSION, blocks: query.serialize()}}
/>
</View>
</View>
</Modal.Body>

View File

@ -22,6 +22,7 @@ import {getByText as domGetByText} from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
import BlockEditor from '../../../BlockEditor'
import {blank_section_with_button_and_heading} from '../../../__tests__/test-content'
import {LATEST_BLOCK_DATA_VERSION} from '../../../utils/transformations'
const user = userEvent.setup()
@ -35,8 +36,7 @@ function renderEditor(props = {}) {
<BlockEditor
enableResizer={false} // jsdom doesn't render enough for BlockResizer to work
container={container}
version="1"
content={blank_section_with_button_and_heading}
content={{version: LATEST_BLOCK_DATA_VERSION, blocks: blank_section_with_button_and_heading}}
onCancel={() => {}}
{...props}
/>,

View File

@ -0,0 +1,60 @@
/*
* 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 {
transform,
transform_0_1_to_0_2,
LATEST_BLOCK_DATA_VERSION,
type BlockEditorData,
type BlockEditorDataTypes,
type BlockEditorData_0_1,
} from '../transformations'
describe('transformations', () => {
it('returns the same data if the version is the latest', () => {
const data: BlockEditorData = {
version: LATEST_BLOCK_DATA_VERSION,
blocks: 'blocks',
}
expect(transform(data)).toEqual(data)
})
it('transforms version 0.1 to the latest version', () => {
const data: BlockEditorDataTypes = {
version: '0.1',
blocks: [{data: 'blocks'}],
}
expect(transform(data)).toEqual({
version: LATEST_BLOCK_DATA_VERSION,
blocks: 'blocks',
})
})
describe('transform 0.1 to 0.2', () => {
it('transforms data', () => {
const data: BlockEditorData_0_1 = {
version: '0.1',
blocks: [{data: 'blocks'}],
}
expect(transform_0_1_to_0_2(data)).toEqual({
version: '0.2',
blocks: 'blocks',
})
})
})
})

View File

@ -0,0 +1,68 @@
/*
* 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/>.
*/
const LATEST_BLOCK_DATA_VERSION = '0.2' as const
type jsonString = string
type BlockEditorData_0_2 = {
version: '0.2'
blocks?: jsonString
}
type BlockType_0_1 = {
data?: jsonString
}
type BlockEditorData_0_1 = {
version: '0.1'
blocks: BlockType_0_1[]
}
// BlockEditorData is the latest version of the data structure
type BlockEditorData = BlockEditorData_0_2
// any possible version of the data structure
type BlockEditorDataTypes = BlockEditorData | BlockEditorData_0_1
const transform = (data: BlockEditorDataTypes): BlockEditorData => {
let transformedData = data
if (data.version === '0.1') {
transformedData = transform_0_1_to_0_2(data)
}
return transformedData as BlockEditorData
}
function transform_0_1_to_0_2(data: BlockEditorData_0_1): BlockEditorData_0_2 {
const blocks = data.blocks[0].data
return {
version: '0.2',
blocks,
}
}
export {
transform,
transform_0_1_to_0_2,
LATEST_BLOCK_DATA_VERSION,
type BlockEditorData,
type BlockEditorDataTypes,
type BlockEditorData_0_1,
}

View File

@ -258,8 +258,7 @@ export default class WikiPageEditView extends ValidatedFormView {
<Suspense fallback={<div>{I18n.t('Loading...')}</div>}>
<BlockEditor
container={container}
version={blockEditorData.version}
content={blockEditorData.blocks[0].data}
content={blockEditorData}
onCancel={this.cancel.bind(this)}
/>
</Suspense>,
@ -421,11 +420,7 @@ export default class WikiPageEditView extends ValidatedFormView {
}
}
if (window.block_editor) {
this.blockEditorData = {
time: Date.now(),
version: '1',
blocks: [{data: window.block_editor().getBlocks()}],
}
this.blockEditorData = window.block_editor().getBlocks()
}
if (this.reloadView != null) {