Skip to content

Commit

Permalink
feat: TypeScript migration (#189)
Browse files Browse the repository at this point in the history
- Migrated the codebase to TypeScript.
- Provided type definitions for plugin users.
- Restructured folders.
- Refactored and optimized the codebase.
- Added the missing MaxWords option.
- Updated dependencies.
- Refreshed the plugin icon.
  • Loading branch information
nshenderov committed Dec 12, 2024
1 parent c83d41e commit d24d9a9
Show file tree
Hide file tree
Showing 79 changed files with 3,672 additions and 2,000 deletions.
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
.github
assets
27 changes: 27 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"extends": ["airbnb", "airbnb-typescript", "airbnb/hooks", "prettier"],
"plugins": ["prettier"],
"parserOptions": {
"project": ["./admin/tsconfig.json", "./server/tsconfig.json"]
},
"rules": {
"@typescript-eslint/no-use-before-define": "off",
"class-methods-use-this": "off",
"max-classes-per-file": "off",
"import/prefer-default-export": "off",
"import/no-default-export": "warn",
"import/extensions": "warn",
"react/require-default-props": "warn",
"no-param-reassign": "warn",
"react/jsx-no-bind": "warn",
"no-console": ["warn", { "allow": ["warn", "error"] }]
},
"overrides": [
{
"files": ["server/**/*"],
"rules": {
"import/no-default-export": "off"
}
}
]
}
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
.github
assets
14 changes: 14 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"endOfLine": "lf",
"printWidth": 100,
"arrowParens": "avoid",
"bracketSpacing": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"embeddedLanguageFormatting": "auto"
}
37 changes: 19 additions & 18 deletions .release-it.js → .release-it.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Config } from 'release-it';

const template = `
{{> header}}
Expand All @@ -18,34 +20,33 @@ const template = `
**Full Changelog**: [{{previousTag}}...{{currentTag}}]({{@root.host}}/{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}/compare/{{previousTag}}...{{currentTag}})
`;

module.exports = {
export default {
git: {
commitMessage: "chore: release v${version}",
requireCleanWorkingDir: false
commitMessage: 'chore: release v${version}',
requireCleanWorkingDir: false,
},
github: {
release: true,
releaseName: "v${version}"
releaseName: 'v${version}',
},
npm: {
publish: false
publish: false,
},
plugins: {
"@release-it/conventional-changelog": {
'@release-it/conventional-changelog': {
writerOpts: {
headerPartial: "## What's Changed\n",
mainTemplate: template
mainTemplate: template,
},
preset: {
name: "conventionalcommits",
name: 'conventionalcommits',
types: [
{ type: "feat", section: "🚀 Features" },
{ type: "fix", section: "👾 Bug Fixes" },
{ type: "docs", section: "📚 Docs" },
{ type: "chore", section: "⚙️ Chore" },
{ type: "style", section: "💅 Style" }
]
}
}
}
};
{ type: 'feat', section: '🚀 Features' },
{ type: 'fix', section: '👾 Bug Fixes' },
{ type: 'chore', section: '⚙️ Chore' },
{ type: 'docs', section: '⚙️ Chore' },
],
},
},
},
} satisfies Config;
9 changes: 9 additions & 0 deletions admin/custom.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export {};

declare global {
interface Window {
strapi: {
backendURL: string;
};
}
}
10 changes: 0 additions & 10 deletions admin/jsconfig.json

This file was deleted.

50 changes: 0 additions & 50 deletions admin/src/CKEditorIcon.jsx

This file was deleted.

143 changes: 143 additions & 0 deletions admin/src/components/CKEReact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { useCallback, useRef, useState } from 'react';
import { useField } from '@strapi/strapi/admin';
import { Flex } from '@strapi/design-system';
import { ClassicEditor } from 'ckeditor5';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import 'ckeditor5/ckeditor5.css';

import { useEditorContext } from './EditorProvider';
import { getStoredToken } from '../utils';
import { MediaLib } from './MediaLib';
import type {
StrapiMediaLibPlugin,
StrapiUploadAdapterConfig,
StrapiUploadAdapterPlugin,
} from '../plugins';

export type WordCountPluginStats = {
words: number;
characters: number;
};

export function CKEReact() {
const [mediaLibVisible, setMediaLibVisible] = useState<boolean>(false);
const [editorInstance, setEditorInstance] = useState<ClassicEditor | null>(null);

const { name, disabled, error, preset, wordsLimit, charsLimit, validateInputLength } =
useEditorContext();
const { onChange: fieldOnChange, value: fieldValue } = useField(name);

const wordCounterRef = useRef<HTMLElement>(null);

const toggleMediaLib = useCallback(() => setMediaLibVisible(prev => !prev), [setMediaLibVisible]);

const handleChangeAssets = useCallback(
(newElems: string): void => {
if (!editorInstance) {
throw new Error('CKEditor: Editor instance is not initialized');
}

const viewFragment = editorInstance.data.processor.toView(newElems);
const modelFragment = editorInstance.data.toModel(viewFragment);
editorInstance?.model.insertContent(modelFragment);

toggleMediaLib();
},
[toggleMediaLib, editorInstance]
);

const onEditorReady = (editor: ClassicEditor): void => {
setUpPlugins(editor);
setEditorInstance(editor);
};

const onEditorChange = (_e: any, editor: ClassicEditor): void => {
const data = editor.getData();
fieldOnChange(name, data);
};

if (!preset) {
return null;
}

return (
<>
<CKEditor
editor={ClassicEditor}
config={preset.editorConfig}
disabled={disabled}
data={fieldValue ?? ''}
onReady={onEditorReady}
onChange={onEditorChange}
/>
<Flex ref={wordCounterRef} color={error ? 'danger600' : 'neutral400'} />
<MediaLib
isOpen={mediaLibVisible}
toggle={toggleMediaLib}
handleChangeAssets={handleChangeAssets}
/>
</>
);

function setUpPlugins(editor: ClassicEditor): void {
const pluginsToSetup: Record<string, (editor: ClassicEditor) => void> = {
WordCount: setUpWordCount,
ImageUploadEditing: setUpImageUploadEditing,
StrapiMediaLib: setUpStrapiMediaLib,
StrapiUploadAdapter: setUpStrapiUploadAdapter,
};

Object.entries(pluginsToSetup).forEach(([pluginName, setUpFn]) => {
if (editor.plugins.has(pluginName)) {
try {
setUpFn(editor);
} catch (err) {
console.error(`CKEditor: Error setting up ${pluginName} plugin `, err);
}
}
});
}

function setUpWordCount(editor: ClassicEditor): void {
const wordCountPlugin = editor.plugins.get('WordCount');

if (wordsLimit || charsLimit) {
wordCountPlugin.on('update', (_e, stats: WordCountPluginStats) => validateInputLength(stats));
}

wordCounterRef.current?.appendChild(wordCountPlugin.wordCountContainer);
}

function setUpImageUploadEditing(editor: ClassicEditor): void {
const imageUploadEditingPlugin = editor.plugins.get('ImageUploadEditing');

const setAltAttribute = (_e: any, { data, imageElement }: any) => {
editor.model.change(writer => {
writer.setAttribute('alt', data.alt, imageElement);
});
};

imageUploadEditingPlugin.on('uploadComplete', setAltAttribute);
}

function setUpStrapiMediaLib(editor: ClassicEditor): void {
const strapiMediaLibPlugin = editor.plugins.get('StrapiMediaLib') as StrapiMediaLibPlugin;
strapiMediaLibPlugin.connect(toggleMediaLib);
}

function setUpStrapiUploadAdapter(editor: ClassicEditor): void {
const StrapiUploadAdapterPlugin = editor.plugins.get(
'StrapiUploadAdapter'
) as StrapiUploadAdapterPlugin;
const token = getStoredToken();
const { backendURL } = window.strapi;
const config: StrapiUploadAdapterConfig = {
uploadUrl: `${backendURL}/upload`,
backendUrl: backendURL,
headers: { Authorization: `Bearer ${token}` },
responsive: window.SH_CKE_UPLOAD_ADAPTER_IS_RESPONSIVE,
};

StrapiUploadAdapterPlugin.initAdapter(config);
}
}
31 changes: 31 additions & 0 deletions admin/src/components/CKEditorIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { styled } from 'styled-components';
import { lightTheme, Flex } from '@strapi/design-system';

export function CKEditorIcon() {
return (
<IconBox justifyContent="center" alignItems="center" width={7} height={6} hasRadius aria-hidden>
<SvgIcon />
</IconBox>
);
}

function SvgIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-8 -7 37 37">
<path
d="M15.04 16.726a3.98 3.98 0 0 0-1.535.524 3.96 3.96 0 0 0-1.402 1.364c-.108.18-.716 1.347-.716 1.347l-2.09 3.82.022.016c.097.063.2.116.308.159.317.113.65.175.987.18 1.264.058 2.529-.016 3.793-.007.725.023 1.45.005 2.172-.053.348-.017.687-.117.99-.29.31-.178.576-.423.78-.717.138-.205.283-.407.409-.62.6-1.02 1.199-2.043 1.794-3.065.187-.321.37-.644.555-.966l.281-.481c.236-.392.367-.838.381-1.294l-3.258.057s-3.147-.012-3.472.026Zm2.764.903Z"
fill={lightTheme.colors.secondary700}
/>
<path
d="m7.12 22.61 1.901-3.477.46-.877c-.31-.168-.614-.35-.918-.528-.866-.508-1.766-.957-2.613-1.498a2.459 2.459 0 0 1-.609-.517c-.27-.336-.341-.736-.362-1.15-.052-1.022-.003-2.045-.02-3.068-.01-.487 0-.975.005-1.462.01-.806.384-1.382 1.069-1.783L8.115 7.03c.55-.322 1.102-.642 1.654-.961.127-.073.263-.13.395-.192.68-.321 1.298-.119 1.9.213.039.02.075.045.112.068.306.149.605.313.895.491.794.445 1.587.893 2.378 1.343.239.139.469.292.688.458.485.36.636.875.666 1.445.039.71.017 1.422.013 2.134-.002.698.01 1.396.003 2.094 1.478-.006 3.146 0 3.146 0l1.807-.032c-.006-.73-.016-1.46-.017-2.19 0-1.31.037-2.62-.039-3.928-.061-1.05-.34-2-1.232-2.666a12.549 12.549 0 0 0-1.264-.848c-1.454-.834-2.91-1.664-4.37-2.49-.545-.308-1.067-.659-1.644-.91-.069-.043-.135-.089-.205-.128-1.106-.613-2.24-.992-3.485-.405-.242.115-.49.218-.723.352-1.011.58-2.02 1.166-3.026 1.757-1.271.744-2.54 1.488-3.81 2.234C.705 5.602.025 6.66.012 8.144c-.008.897-.02 1.794 0 2.691.039 1.884-.045 3.77.058 5.652.042.761.174 1.499.672 2.12.32.377.698.7 1.121.956 1.556 1.001 3.209 1.835 4.8 2.775l.457.271Z"
fill={lightTheme.colors.secondary600}
/>
</svg>
);
}

const IconBox = styled(Flex)`
background-color: ${lightTheme.colors.secondary100};
border: 1px solid ${lightTheme.colors.secondary200};
`;
Loading

0 comments on commit d24d9a9

Please sign in to comment.