Skip to content

Commit

Permalink
Merge pull request #4077 from serlo/feat/expose-plugins-in-editor
Browse files Browse the repository at this point in the history
Feat/expose all plugins in editor
  • Loading branch information
CodingDive authored Sep 10, 2024
2 parents bb87f62 + b1092ce commit fda1695
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/editor-web-component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"devDependencies": {
"@rollup/plugin-replace": "^5.0.5",
"@serlo/editor": "0.12.1",
"@serlo/editor": "^0.12.1",
"@serlo/typescript-config": "workspace:*",
"@types/escodegen": "^0.0.10",
"@types/estraverse": "^5.1.7",
Expand Down
2 changes: 2 additions & 0 deletions packages/editor-web-component/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AllPlugins } from '@serlo/editor'
export { EditorWebComponent } from './editor-web-component'
2 changes: 1 addition & 1 deletion packages/editor-web-component/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ function removeGlobalStylesPlugin(): Plugin {
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/editor-web-component.tsx'),
entry: resolve(__dirname, 'src/index.tsx'),
name: 'EditorWebComponent',
fileName: 'serlo-editor-web-component',
formats: ['es'],
Expand Down
22 changes: 22 additions & 0 deletions packages/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,28 @@ See below for the current API specification.

- **`_ltik` (optional)**: Required by the custom plugin `edusharingAsset` only used in `serlo-editor-for-edusharing`. **To be removed once a better solution is found or the plugin is removed.**

### `AllPlugins` constant

Exports a record with following structure which you can use to create your own plugin menu for integrating the Serlo Editor.

```TypeScript
[key: EditorPluginType]: {
de: {
name: string
description: string
}
en: {
name: string
description: string
}
icon: string
type: EditorPluginType
initialState: PluginState
}
```

You can iterate over this structure by using `Object.values(AllPlugins)` to get them as an array, which you can use to sort, filter and modify to your liking.

## Releasing a new version to npm

Bump the version number in the package.json and
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@serlo/editor",
"version": "0.12.1",
"version": "0.13.0",
"homepage": "https://de.serlo.org/editor",
"bugs": {
"url": "https://github.com/serlo/frontend/issues"
Expand Down
8 changes: 8 additions & 0 deletions packages/editor/src/core/svg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare module '*.svg?raw' {
// This lets TypeScript know that we import raw svg strings. This is specific to
// vite.

const content: string
// eslint-disable-next-line import/no-default-export
export default content
}
215 changes: 215 additions & 0 deletions packages/editor/src/package/all-plugins.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// import IconAudio from '@editor/editor-ui/assets/plugin-icons/icon-audio.svg?raw'
import IconScMcExercise from '@editor/editor-ui/assets/plugin-icons/icon-auswahlaufgaben.svg?raw'
import IconBlanksDragAndDrop from '@editor/editor-ui/assets/plugin-icons/icon-blanks-dnd.svg?raw'
import IconBlanksTyping from '@editor/editor-ui/assets/plugin-icons/icon-blanks-typing.svg?raw'
import IconBox from '@editor/editor-ui/assets/plugin-icons/icon-box.svg?raw'
import IconDropzones from '@editor/editor-ui/assets/plugin-icons/icon-dropzones.svg?raw'
import IconEquation from '@editor/editor-ui/assets/plugin-icons/icon-equation.svg?raw'
import IconGeogebra from '@editor/editor-ui/assets/plugin-icons/icon-geogebra.svg?raw'
import IconH5p from '@editor/editor-ui/assets/plugin-icons/icon-h5p.svg?raw'
import IconHighlight from '@editor/editor-ui/assets/plugin-icons/icon-highlight.svg?raw'
import IconImage from '@editor/editor-ui/assets/plugin-icons/icon-image.svg?raw'
import IconInjection from '@editor/editor-ui/assets/plugin-icons/icon-injection.svg?raw'
import IconTextArea from '@editor/editor-ui/assets/plugin-icons/icon-input-exercise.svg?raw'
import IconMultimedia from '@editor/editor-ui/assets/plugin-icons/icon-multimedia.svg?raw'
import IconSpoiler from '@editor/editor-ui/assets/plugin-icons/icon-spoiler.svg?raw'
import IconTable from '@editor/editor-ui/assets/plugin-icons/icon-table.svg?raw'
import IconText from '@editor/editor-ui/assets/plugin-icons/icon-text.svg?raw'
import IconVideo from '@editor/editor-ui/assets/plugin-icons/icon-video.svg?raw'
import IconImageGallery from '@editor/editor-ui/assets/plugin-icons/image-gallery/icon-image-gallery.svg?raw'
import { EditorPluginType } from '@editor/types/editor-plugin-type'

import { loggedInData as loggedInDataDe } from '@/data/de'
import { loggedInData as loggedInDataEn } from '@/data/en'

// Need this tiny interface, because the mergeDeepObject seems to
// screw up the types of our i18n strings
type PluginStrings = Record<
EditorPluginType,
{
title: string
description: string
}
>

const germanPluginStrings = loggedInDataDe.strings.editor
.plugins as unknown as PluginStrings

const englishPluginStrings = loggedInDataEn.strings.editor
.plugins as unknown as PluginStrings

const getPluginNameAndDescription = (
locale: 'de' | 'en',
pluginType: EditorPluginType
) => {
let name: string
let description: string
if (locale === 'de') {
name = germanPluginStrings[pluginType]?.title
description = germanPluginStrings[pluginType]?.description
} else if (locale === 'en') {
name = englishPluginStrings[pluginType]?.title
description = englishPluginStrings[pluginType]?.description
} else {
throw new Error('Invalid locale')
}

if (!name || !description) {
throw new Error(
'Missing plugin name or description for plugin type' + pluginType
)
}

return {
name,
description,
}
}

const getInternationalizedPluginStrings = (type: EditorPluginType) => ({
de: getPluginNameAndDescription('de', type),
en: getPluginNameAndDescription('en', type),
})

interface PluginState {
plugin: EditorPluginType
state?: any
}

interface PluginInfo {
de: {
name: string
description: string
}
en: {
name: string
description: string
}
icon: string
type: EditorPluginType
initialState: PluginState
}

function pluginFactory(type: EditorPluginType, icon: string): PluginInfo {
const internationalizedStrings = getInternationalizedPluginStrings(type)

let initialState: PluginState = {
// ? All plugins and the migration algorithm seem to be reliant on this
// structure. How could we remove the rows plugin from the initial state?
plugin: EditorPluginType.Rows,
state: [{ plugin: type }],
}

switch (type) {
// Following types need to be wrapped in exercises
case EditorPluginType.ScMcExercise:
case EditorPluginType.InputExercise:
case EditorPluginType.TextAreaExercise:
case EditorPluginType.BlanksExercise:
case EditorPluginType.BlanksExerciseDragAndDrop:
initialState = {
plugin: EditorPluginType.Rows,
state: [
{
plugin: EditorPluginType.Exercise,
state: {
content: {
plugin: EditorPluginType.Rows,
state: [{ plugin: EditorPluginType.Text }],
},
interactive: { plugin: type },
},
},
],
}
break
case EditorPluginType.Text:
case EditorPluginType.Image:
case EditorPluginType.Video:
case EditorPluginType.Highlight:
case EditorPluginType.Spoiler:
case EditorPluginType.Box:
case EditorPluginType.SerloTable:
case EditorPluginType.Equations:
case EditorPluginType.Geogebra:
case EditorPluginType.Injection:
case EditorPluginType.H5p:
case EditorPluginType.Multimedia:
case EditorPluginType.DropzoneImage:
case EditorPluginType.ImageGallery:
break
default:
console.warn(`Unhandled plugin type: ${type}`)
}

return {
...internationalizedStrings,
icon,
type,
initialState,
}
}

export const AllPlugins = {
[EditorPluginType.Text]: pluginFactory(EditorPluginType.Text, IconText),
[EditorPluginType.Multimedia]: pluginFactory(
EditorPluginType.Multimedia,
IconMultimedia
),
[EditorPluginType.Video]: pluginFactory(EditorPluginType.Video, IconVideo),
[EditorPluginType.Box]: pluginFactory(EditorPluginType.Box, IconBox),
[EditorPluginType.Equations]: pluginFactory(
EditorPluginType.Equations,
IconEquation
),
[EditorPluginType.Geogebra]: pluginFactory(
EditorPluginType.Geogebra,
IconGeogebra
),
[EditorPluginType.H5p]: pluginFactory(EditorPluginType.H5p, IconH5p),
[EditorPluginType.Highlight]: pluginFactory(
EditorPluginType.Highlight,
IconHighlight
),
[EditorPluginType.Image]: pluginFactory(EditorPluginType.Image, IconImage),
[EditorPluginType.ImageGallery]: pluginFactory(
EditorPluginType.ImageGallery,
IconImageGallery
),
[EditorPluginType.Injection]: pluginFactory(
EditorPluginType.Injection,
IconInjection
),
[EditorPluginType.SerloTable]: pluginFactory(
EditorPluginType.SerloTable,
IconTable
),
[EditorPluginType.Spoiler]: pluginFactory(
EditorPluginType.Spoiler,
IconSpoiler
),
[EditorPluginType.DropzoneImage]: pluginFactory(
EditorPluginType.DropzoneImage,
IconDropzones
),
[EditorPluginType.ScMcExercise]: pluginFactory(
EditorPluginType.ScMcExercise,
IconScMcExercise
),
[EditorPluginType.InputExercise]: pluginFactory(
EditorPluginType.InputExercise,
IconTextArea
),
[EditorPluginType.TextAreaExercise]: pluginFactory(
EditorPluginType.TextAreaExercise,
IconTextArea
),
[EditorPluginType.BlanksExercise]: pluginFactory(
EditorPluginType.BlanksExercise,
IconBlanksTyping
),
[EditorPluginType.BlanksExerciseDragAndDrop]: pluginFactory(
EditorPluginType.BlanksExerciseDragAndDrop,
IconBlanksDragAndDrop
),
}
1 change: 1 addition & 0 deletions packages/editor/src/package/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { SerloEditor, type SerloEditorProps } from './editor'
export { SerloRenderer, type SerloRendererProps } from './serlo-renderer'

export type { BaseEditor } from '@editor/core'
export { AllPlugins } from './all-plugins'

export { EditorPluginType } from '@editor/types/editor-plugin-type'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function AddRowButtonFloating(props: AddRowButtonFloatingProps) {
hover:opacity-100 hover:z-50
focus:opacity-100 focus:z-50
${focused ? 'opacity-40' : ''}
focus-visible:text-editor-primary-200
focus-visible:text-editor-primary-200
`

const interactionStyles = 'cursor-pointer'
Expand All @@ -37,6 +37,9 @@ export function AddRowButtonFloating(props: AddRowButtonFloatingProps) {
ref={buttonRef}
className={cn(baseStyles, stateStyles, interactionStyles)}
onClick={onClick}
// needed so that the "add plugin" row can be hidden in CSS for editor
// integrations: [data-button-type="add-row"] { display: none; }
data-button-type="add-row"
>
{/* Divider line */}
<span className="flex-grow border-t-2 border-gray-300" />
Expand Down
1 change: 1 addition & 0 deletions packages/editor/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default defineConfig({
'@': resolve(__dirname, '../../apps/web/src'),
},
},
assetsInclude: ['./src/editor-ui/assets/plugin-icons/**/*.svg'],
plugins: [
replace({ ...envReplacements, preventAssignment: false }),
react(),
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5189,7 +5189,7 @@ __metadata:
resolution: "@serlo/editor-web-component@workspace:packages/editor-web-component"
dependencies:
"@rollup/plugin-replace": ^5.0.5
"@serlo/editor": 0.12.1
"@serlo/editor": ^0.12.1
"@serlo/typescript-config": "workspace:*"
"@types/escodegen": ^0.0.10
"@types/estraverse": ^5.1.7
Expand Down Expand Up @@ -5224,7 +5224,7 @@ __metadata:
languageName: unknown
linkType: soft

"@serlo/editor@npm:0.12.1":
"@serlo/editor@npm:^0.12.1":
version: 0.12.1
resolution: "@serlo/editor@npm:0.12.1"
dependencies:
Expand Down

0 comments on commit fda1695

Please sign in to comment.