├── packages ├── kg-simplemde │ ├── .nvmrc │ ├── yarn.lock │ ├── .gitignore │ ├── .eslintrc │ └── bower.json ├── kg-converters │ ├── .eslintignore │ ├── .gitignore │ ├── index.js │ ├── lib │ │ └── kg-converters.js │ ├── test │ │ ├── .eslintrc.js │ │ └── exports.test.js │ ├── .eslintrc.js │ └── README.md ├── kg-clean-basic-html │ ├── .eslintignore │ ├── .gitignore │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ ├── .eslintrc.js │ └── README.md ├── kg-default-nodes │ ├── .eslintignore │ ├── index.js │ ├── .gitignore │ ├── lib │ │ ├── utils │ │ │ ├── is-unsplash-image.js │ │ │ ├── is-local-content-image.js │ │ │ ├── escape-html.js │ │ │ ├── slugify.js │ │ │ ├── clean-dom.js │ │ │ ├── build-clean-basic-html-for-element.js │ │ │ ├── read-caption-from-element.js │ │ │ ├── render-empty-container.js │ │ │ ├── get-resized-image-dimensions.js │ │ │ ├── tagged-template-fns.mjs │ │ │ ├── rgb-to-hex.js │ │ │ ├── size-byte-converter.js │ │ │ └── add-create-document-option.js │ │ ├── nodes │ │ │ ├── at-link │ │ │ │ ├── index.js │ │ │ │ └── kg-link.svg │ │ │ ├── horizontalrule │ │ │ │ ├── horizontalrule-parser.js │ │ │ │ ├── horizontalrule-renderer.js │ │ │ │ └── HorizontalRuleNode.js │ │ │ ├── paywall │ │ │ │ ├── paywall-renderer.js │ │ │ │ ├── paywall-parser.js │ │ │ │ └── PaywallNode.js │ │ │ ├── email │ │ │ │ └── EmailNode.js │ │ │ ├── markdown │ │ │ │ ├── markdown-renderer.js │ │ │ │ └── MarkdownNode.js │ │ │ ├── aside │ │ │ │ └── AsideParser.js │ │ │ ├── html │ │ │ │ └── HtmlNode.js │ │ │ ├── toggle │ │ │ │ └── ToggleNode.js │ │ │ ├── button │ │ │ │ └── ButtonNode.js │ │ │ └── audio │ │ │ │ └── AudioNode.js │ │ ├── KoenigDecoratorNode.js │ │ └── serializers │ │ │ └── paragraph.js │ ├── test │ │ ├── .eslintrc.js │ │ ├── test-utils │ │ │ ├── overrides.js │ │ │ └── assertions.js │ │ └── utils │ │ │ └── rgb-to-hex.test.js │ └── .eslintrc.js ├── kg-parser-plugins │ ├── .eslintignore │ ├── .gitignore │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ ├── lib │ │ └── cards │ │ │ └── soft-return.js │ └── .eslintrc.js ├── kg-html-to-lexical │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ └── decs.d.ts │ ├── .eslintrc.js │ ├── test │ │ └── .eslintrc.js │ └── README.md ├── kg-default-transforms │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── typings.d.ts │ │ ├── kg-default-nodes.d.ts │ │ └── transforms │ │ │ ├── remove-alignment.ts │ │ │ └── merge-list-nodes.ts │ ├── .eslintrc.js │ ├── test │ │ └── .eslintrc.js │ └── README.md ├── kg-utils │ ├── index.js │ ├── lib │ │ └── kg-utils.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ ├── README.md │ └── package.json ├── kg-default-atoms │ ├── index.js │ ├── lib │ │ └── atoms │ │ │ ├── index.js │ │ │ └── soft-return.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ ├── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ │ └── atoms │ │ │ └── soft-return.test.js │ ├── README.md │ └── package.json ├── kg-default-cards │ ├── index.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ ├── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ │ ├── cards │ │ │ ├── hr.test.js │ │ │ └── paywall.test.js │ │ └── lib │ │ │ └── utils │ │ │ └── is-unsplash-image.test.js │ ├── lib │ │ ├── utils │ │ │ ├── is-unsplash-image.js │ │ │ ├── dedent.js │ │ │ ├── is-local-content-image.js │ │ │ ├── hbs.js │ │ │ ├── index.js │ │ │ └── resize-image.js │ │ └── cards │ │ │ ├── hr.js │ │ │ ├── paywall.js │ │ │ └── index.js │ └── README.md ├── kg-lexical-html-renderer │ ├── .gitignore │ ├── lib │ │ ├── index.ts │ │ ├── kg-utils.d.ts │ │ ├── transformers │ │ │ ├── element │ │ │ │ ├── paragraph.ts │ │ │ │ ├── heading.ts │ │ │ │ ├── aside.ts │ │ │ │ └── blockquote.ts │ │ │ └── index.ts │ │ ├── utils │ │ │ └── generate-id.ts │ │ └── get-dynamic-data-nodes.ts │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── assertions.js │ │ │ ├── index.js │ │ │ ├── overrides.js │ │ │ └── should-render.js │ └── .eslintrc.js ├── koenig-lexical │ ├── .lintstagedrc │ ├── src │ │ ├── utils │ │ │ ├── isGif.js │ │ │ ├── ctrlOrCmd.js │ │ │ ├── constants.js │ │ │ ├── getImageFilenameFromSrc.js │ │ │ ├── openFileSelection.js │ │ │ ├── index.js │ │ │ ├── storybook │ │ │ │ └── populate-storybook-editor.js │ │ │ ├── getAccentColor.js │ │ │ ├── getTopLevelNativeElement.js │ │ │ ├── $selectDecoratorNode.js │ │ │ ├── shortcutSymbols.js │ │ │ ├── prettifyFileName.js │ │ │ ├── analytics.js │ │ │ ├── getEditorCardNodes.js │ │ │ ├── isInternalUrl.js │ │ │ ├── autoExpandTextArea.js │ │ │ ├── getAudioMetadata.js │ │ │ ├── getImageDimensions.js │ │ │ ├── getScrollParent.js │ │ │ ├── callToActionColors.js │ │ │ ├── isEditorEmpty.js │ │ │ ├── getSelectedNode.js │ │ │ ├── thumbnailUploadHandler.js │ │ │ ├── dataSrcToFile.js │ │ │ ├── shouldIgnoreEvent.js │ │ │ ├── getDOMRangeRect.js │ │ │ ├── sanitize-html.js │ │ │ └── $insertAndSelectNode.js │ │ ├── context │ │ │ ├── CardContext.jsx │ │ │ ├── KoenigComposerContext.jsx │ │ │ ├── SharedOnChangeContext.jsx │ │ │ └── SharedHistoryContext.jsx │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── SlashMenu.jsx │ │ │ │ ├── cards │ │ │ │ │ ├── HorizontalRuleCard.jsx │ │ │ │ │ └── PaywallCard.jsx │ │ │ │ ├── ReadOnlyOverlay.jsx │ │ │ │ ├── EditorPlaceholder.jsx │ │ │ │ ├── TextInput.jsx │ │ │ │ ├── CardVisibilityMessage.jsx │ │ │ │ ├── FileUploadForm.jsx │ │ │ │ ├── AudioUploadForm.jsx │ │ │ │ ├── Input.stories.jsx │ │ │ │ ├── Toggle.stories.jsx │ │ │ │ ├── MediaPlayer.stories.jsx │ │ │ │ ├── ActionToolbar.jsx │ │ │ │ ├── PlusMenu.stories.jsx │ │ │ │ ├── Slider.stories.jsx │ │ │ │ ├── SubscribeForm.stories.jsx │ │ │ │ ├── file-selectors │ │ │ │ │ ├── UnsplashModal.jsx │ │ │ │ │ └── Tenor │ │ │ │ │ │ ├── Error.jsx │ │ │ │ │ │ └── Loader.jsx │ │ │ │ ├── ProgressBar.stories.jsx │ │ │ │ ├── Delayed.jsx │ │ │ │ ├── IconButton.stories.jsx │ │ │ │ ├── ProgressBar.jsx │ │ │ │ ├── Tooltip.jsx │ │ │ │ ├── LinkToolbar.stories.jsx │ │ │ │ ├── ImageUploadForm.jsx │ │ │ │ ├── LinkInput.stories.jsx │ │ │ │ ├── IconButton.jsx │ │ │ │ ├── LinkInputWithSearch.stories.jsx │ │ │ │ ├── HighlightedString.jsx │ │ │ │ ├── EmojiPicker.jsx │ │ │ │ ├── ImageUploadSwatch.jsx │ │ │ │ ├── ColorPicker.stories.jsx │ │ │ │ └── Dropdown.stories.jsx │ │ │ ├── KoenigErrorBoundary.jsx │ │ │ └── KoenigEditor.jsx │ │ ├── assets │ │ │ └── icons │ │ │ │ ├── kg-add.svg │ │ │ │ ├── kg-close.svg │ │ │ │ ├── kg-align-center.svg │ │ │ │ ├── kg-align-left.svg │ │ │ │ ├── kg-card-type-unsplash.svg │ │ │ │ ├── kg-heading-2.svg │ │ │ │ ├── kg-card-type-divider.svg │ │ │ │ ├── kg-expand.svg │ │ │ │ ├── kg-heading-3.svg │ │ │ │ ├── kg-italic.svg │ │ │ │ ├── kg-play.svg │ │ │ │ ├── kg-unsplash-heart.svg │ │ │ │ ├── kg-card-type-html.svg │ │ │ │ ├── kg-arrow-top-right.svg │ │ │ │ ├── kg-card-type-email-cta.svg │ │ │ │ ├── kg-download.svg │ │ │ │ ├── kg-indicator-html.svg │ │ │ │ ├── plus.svg │ │ │ │ ├── kg-bold.svg │ │ │ │ ├── kg-card-type-bookmark.svg │ │ │ │ ├── kg-quote.svg │ │ │ │ ├── kg-card-type-collection.svg │ │ │ │ ├── kg-arrow-down.svg │ │ │ │ ├── kg-card-type-x.svg │ │ │ │ ├── kg-card-type-button.svg │ │ │ │ ├── kg-quote-1.svg │ │ │ │ ├── kg-card-type-header.svg │ │ │ │ ├── kg-edit.svg │ │ │ │ ├── kg-file-placeholder.svg │ │ │ │ ├── kg-snippet.svg │ │ │ │ ├── kg-unmute.svg │ │ │ │ ├── kg-card-type-gif.svg │ │ │ │ ├── kg-search.svg │ │ │ │ ├── kg-card-type-snippet.svg │ │ │ │ ├── kg-card-type-video.svg │ │ │ │ ├── kg-card-type-product.svg │ │ │ │ ├── kg-img-wide.svg │ │ │ │ ├── kg-img-regular.svg │ │ │ │ ├── kg-card-type-other.svg │ │ │ │ ├── kg-layout-split.svg │ │ │ │ ├── kg-eyedropper.svg │ │ │ │ ├── kg-indicator-markdown.svg │ │ │ │ ├── kg-img-full.svg │ │ │ │ ├── kg-shrink.svg │ │ │ │ ├── kg-toggle-arrow.svg │ │ │ │ ├── kg-star.svg │ │ │ │ ├── kg-layout-immersive.svg │ │ │ │ ├── kg-gallery-placeholder.svg │ │ │ │ ├── kg-card-type-file.svg │ │ │ │ ├── kg-file-upload.svg │ │ │ │ ├── kg-product-placeholder.svg │ │ │ │ ├── kg-quote-2.svg │ │ │ │ ├── kg-card-type-audio.svg │ │ │ │ ├── kg-card-type-markdown.svg │ │ │ │ ├── kg-img-placeholder.svg │ │ │ │ ├── kg-card-type-signup.svg │ │ │ │ ├── kg-img-bg.svg │ │ │ │ ├── kg-replace.svg │ │ │ │ ├── kg-card-type-preview.svg │ │ │ │ ├── kg-card-type-toggle.svg │ │ │ │ ├── kg-layout-list.svg │ │ │ │ ├── kg-swap.svg │ │ │ │ ├── kg-video-placeholder.svg │ │ │ │ ├── kg-layout-minimal.svg │ │ │ │ ├── kg-link.svg │ │ │ │ ├── kg-card-type-callout.svg │ │ │ │ ├── kg-card-type-gen-embed.svg │ │ │ │ ├── kg-wand.svg │ │ │ │ ├── kg-eye.svg │ │ │ │ ├── kg-trash.svg │ │ │ │ ├── kg-sync.svg │ │ │ │ ├── kg-audio-file.svg │ │ │ │ ├── kg-card-type-email.svg │ │ │ │ ├── kg-audio-placeholder.svg │ │ │ │ ├── kg-indicator-email.svg │ │ │ │ ├── kg-layout-grid.svg │ │ │ │ ├── kg-card-type-youtube.svg │ │ │ │ ├── kg-eye-closed.svg │ │ │ │ ├── kg-card-type-gallery.svg │ │ │ │ ├── kg-upload-fill.svg │ │ │ │ ├── kg-card-type-twitter.svg │ │ │ │ ├── kg-card-type-image.svg │ │ │ │ ├── kg-help.svg │ │ │ │ └── kg-card-type-spotify.svg │ │ ├── nodes │ │ │ ├── MinimalNodes.js │ │ │ └── BasicNodes.js │ │ ├── plugins │ │ │ ├── TKCountPlugin.jsx │ │ │ ├── CardMenuPlugin.jsx │ │ │ ├── KoenigBlurPlugin.jsx │ │ │ └── KoenigFocusPlugin.jsx │ │ ├── styles │ │ │ └── components │ │ │ │ └── react-colorful.css │ │ └── hooks │ │ │ ├── useClickOutside.js │ │ │ └── usePreviousFocus.js │ ├── test │ │ ├── utils │ │ │ └── isTestEnv.js │ │ ├── test-setup.js │ │ ├── e2e │ │ │ ├── fixtures │ │ │ │ ├── video.mp4 │ │ │ │ ├── print-img.pdf │ │ │ │ ├── video-fail.mp4 │ │ │ │ ├── audio-sample.mp3 │ │ │ │ ├── large-image.jpeg │ │ │ │ ├── large-image.png │ │ │ │ ├── large-image-0.png │ │ │ │ ├── large-image-1.png │ │ │ │ ├── large-image-2.png │ │ │ │ ├── large-image-3.png │ │ │ │ ├── large-image-4.png │ │ │ │ ├── large-image-5.png │ │ │ │ ├── large-image-6.png │ │ │ │ ├── large-image-7.png │ │ │ │ ├── large-image-8.png │ │ │ │ ├── large-image-9.png │ │ │ │ ├── audio-sample-fail.mp3 │ │ │ │ └── large-image-fail.jpeg │ │ │ └── plugins │ │ │ │ └── assets │ │ │ │ ├── large.jpeg │ │ │ │ └── large.png │ │ └── unit │ │ │ ├── assets │ │ │ ├── large.jpeg │ │ │ └── large.png │ │ │ └── KoenigComposer.test.jsx │ ├── .stylelintrc.json │ ├── public │ │ ├── Koenig-editor-1.png │ │ ├── Koenig-editor-2.png │ │ └── assets │ │ │ └── fonts │ │ │ └── Inter.ttf │ ├── postcss.config.cjs │ ├── check-publish-env.js │ ├── .storybook │ │ ├── editorEmptyState.js │ │ └── preview-head.html │ ├── demo │ │ ├── components │ │ │ ├── icons │ │ │ │ ├── eye-closed.svg │ │ │ │ └── eye-open.svg │ │ │ ├── Navigator.jsx │ │ │ ├── DarkModeToggle.jsx │ │ │ ├── WordCount.jsx │ │ │ ├── FloatingButton.jsx │ │ │ ├── Sidebar.jsx │ │ │ └── TreeView.jsx │ │ ├── utils │ │ │ ├── unsplashConfig.js │ │ │ └── tenorConfig.js │ │ ├── assets │ │ │ └── icons │ │ │ │ ├── kg-lock.svg │ │ │ │ └── kg-dollar.svg │ │ └── content │ │ │ └── minimal-content.json │ ├── svgo.config.js │ ├── .gitignore │ └── index.html ├── html-to-mobiledoc │ ├── index.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ └── package.json ├── kg-card-factory │ ├── index.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ ├── README.md │ └── package.json ├── kg-markdown-html-renderer │ ├── index.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ └── README.md ├── kg-mobiledoc-html-renderer │ ├── index.js │ ├── .eslintrc.js │ ├── test │ │ ├── .eslintrc.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── assertions.js │ │ │ └── overrides.js │ └── README.md └── kg-unsplash-selector │ ├── src │ ├── styles │ │ └── index.css │ ├── vite-env.d.ts │ ├── assets │ │ ├── kg-card-type-unsplash.svg │ │ ├── kg-unsplash-heart.svg │ │ ├── kg-close.svg │ │ ├── kg-download.svg │ │ └── kg-search.svg │ ├── index.ts │ └── api │ │ ├── unsplashFixtures.ts │ │ └── IUnsplashProvider.ts │ ├── demo │ ├── vite-env.d.ts │ └── demo.tsx │ ├── postcss.config.cjs │ ├── tsconfig.declaration.json │ ├── tsconfig.node.json │ ├── index.html │ ├── .gitignore │ ├── vitest.config.ts │ └── tsconfig.json ├── .github ├── workflows │ └── CNAME ├── hooks │ └── pre-commit └── renovate.json ├── .lintstagedrc ├── gh-pages.config.json ├── .vscode └── settings.json ├── .editorconfig └── lerna.json /packages/kg-simplemde/.nvmrc: -------------------------------------------------------------------------------- 1 | 8 -------------------------------------------------------------------------------- /.github/workflows/CNAME: -------------------------------------------------------------------------------- 1 | https://koenig.ghost.org -------------------------------------------------------------------------------- /packages/kg-converters/.eslintignore: -------------------------------------------------------------------------------- 1 | cjs 2 | es 3 | -------------------------------------------------------------------------------- /packages/kg-converters/.gitignore: -------------------------------------------------------------------------------- 1 | cjs/ 2 | es/ 3 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/.eslintignore: -------------------------------------------------------------------------------- 1 | cjs 2 | es 3 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/.gitignore: -------------------------------------------------------------------------------- 1 | cjs/ 2 | es/ 3 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/.eslintignore: -------------------------------------------------------------------------------- 1 | cjs 2 | es 3 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/.eslintignore: -------------------------------------------------------------------------------- 1 | cjs 2 | es 3 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/.gitignore: -------------------------------------------------------------------------------- 1 | cjs/ 2 | es/ 3 | -------------------------------------------------------------------------------- /packages/kg-converters/index.js: -------------------------------------------------------------------------------- 1 | export * from './lib/kg-converters'; 2 | -------------------------------------------------------------------------------- /packages/kg-html-to-lexical/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tsconfig.tsbuildinfo 3 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "packages/**/*.{js,jsx,ts,tsx,cjs}": "eslint" 3 | } -------------------------------------------------------------------------------- /packages/kg-default-nodes/index.js: -------------------------------------------------------------------------------- 1 | export * from './lib/kg-default-nodes'; 2 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tsconfig.tsbuildinfo 3 | -------------------------------------------------------------------------------- /packages/kg-utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/kg-utils'); 2 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/atoms'); 2 | -------------------------------------------------------------------------------- /packages/kg-default-cards/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/cards'); 2 | -------------------------------------------------------------------------------- /packages/kg-html-to-lexical/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './html-to-lexical'; 2 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tsconfig.tsbuildinfo 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts,tsx,cjs}": "eslint" 3 | } -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/converter'); 2 | -------------------------------------------------------------------------------- /packages/kg-card-factory/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/CardFactory'); 2 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/.gitignore: -------------------------------------------------------------------------------- 1 | cjs/ 2 | es/ 3 | build/ 4 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /packages/kg-default-transforms/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './default-transforms.js'; 2 | -------------------------------------------------------------------------------- /packages/kg-html-to-lexical/src/decs.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@tryghost/kg-default-nodes'; 2 | -------------------------------------------------------------------------------- /packages/kg-utils/lib/kg-utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | slugify: require('./slugify') 3 | }; 4 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/markdown-html-renderer'); 2 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/MobiledocHtmlRenderer'); 2 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/isGif.js: -------------------------------------------------------------------------------- 1 | export function isGif(url) { 2 | return /\.(gif)$/.test(url); 3 | } 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/utils/isTestEnv.js: -------------------------------------------------------------------------------- 1 | export const isTestEnv = import.meta.env.VITE_TEST === 'true'; 2 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/styles/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/ctrlOrCmd.js: -------------------------------------------------------------------------------- 1 | export default navigator.userAgent.indexOf('Mac') !== -1 ? 'Cmd' : 'Ctrl'; 2 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/test-setup.js: -------------------------------------------------------------------------------- 1 | // add custom jest matchers for DOM state 2 | import '@testing-library/jest-dom'; 3 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/lib/atoms/index.js: -------------------------------------------------------------------------------- 1 | const softReturn = require('./soft-return'); 2 | 3 | module.exports = [softReturn]; 4 | -------------------------------------------------------------------------------- /packages/kg-simplemde/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "at-rule-disallowed-list": ["apply"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const IMAGE_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg', 'svgz', 'webp']; 2 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/index.ts: -------------------------------------------------------------------------------- 1 | import LexicalHTMLRenderer from './LexicalHTMLRenderer'; 2 | 3 | export = LexicalHTMLRenderer; 4 | -------------------------------------------------------------------------------- /packages/kg-utils/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/public/Koenig-editor-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/public/Koenig-editor-1.png -------------------------------------------------------------------------------- /packages/koenig-lexical/public/Koenig-editor-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/public/Koenig-editor-2.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/video.mp4 -------------------------------------------------------------------------------- /packages/koenig-lexical/test/unit/assets/large.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/unit/assets/large.jpeg -------------------------------------------------------------------------------- /packages/koenig-lexical/test/unit/assets/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/unit/assets/large.png -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-card-factory/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-default-cards/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-utils/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/public/assets/fonts/Inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/public/assets/fonts/Inter.ttf -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-card-factory/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-simplemde/.gitignore: -------------------------------------------------------------------------------- 1 | localtesting/ 2 | node_modules/ 3 | bower_components/ 4 | 5 | #For IDE 6 | *.iml 7 | *.ipr 8 | *.iws 9 | .idea/ 10 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/print-img.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/print-img.pdf -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/video-fail.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/video-fail.mp4 -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/utils/is-unsplash-image.js: -------------------------------------------------------------------------------- 1 | module.exports = function isUnsplashImage(url) { 2 | return /images\.unsplash\.com/.test(url); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/is-unsplash-image.js: -------------------------------------------------------------------------------- 1 | export const isUnsplashImage = function (url) { 2 | return /images\.unsplash\.com/.test(url); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/audio-sample.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/audio-sample.mp3 -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image.jpeg -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/plugins/assets/large.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/plugins/assets/large.jpeg -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/plugins/assets/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/plugins/assets/large.png -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/context/CardContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CardContext = React.createContext({}); 4 | 5 | export default CardContext; 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-0.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-1.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-2.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-3.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-4.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-5.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-6.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-7.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-8.png -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-9.png -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/at-link/index.js: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | export * from './AtLinkNode'; 3 | export * from './AtLinkSearchNode'; 4 | /* c8 ignore end */ 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/audio-sample-fail.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/audio-sample-fail.mp3 -------------------------------------------------------------------------------- /packages/koenig-lexical/test/e2e/fixtures/large-image-fail.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/Koenig/HEAD/packages/koenig-lexical/test/e2e/fixtures/large-image-fail.jpeg -------------------------------------------------------------------------------- /packages/kg-parser-plugins/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | module.exports = { 3 | plugins: ['ghost'], 4 | extends: [ 5 | 'plugin:ghost/test' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | module.exports = { 3 | plugins: ['ghost'], 4 | extends: [ 5 | 'plugin:ghost/test' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/cards/hr.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'hr', 3 | type: 'dom', 4 | 5 | render({env: {dom}}) { 6 | return dom.createElement('hr'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/kg-html-to-lexical/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['ghost'], 4 | extends: [ 5 | 'plugin:ghost/ts' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/demo/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMeta { 2 | readonly env: { 3 | readonly VITE_APP_DEMO: string; 4 | readonly VITE_APP_TESTING: string; 5 | } 6 | } -------------------------------------------------------------------------------- /packages/koenig-lexical/src/context/KoenigComposerContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const KoenigComposerContext = React.createContext({}); 4 | 5 | export default KoenigComposerContext; 6 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['ghost'], 4 | extends: [ 5 | 'plugin:ghost/ts' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/kg-utils.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@tryghost/kg-utils' { 2 | export function slugify (text: string, options?: {ghostVersion?: string, type?: string}): string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/utils/dedent.js: -------------------------------------------------------------------------------- 1 | module.exports = function dedent(str) { 2 | let lines = str.split(/\n/); 3 | return lines.map(line => line.replace(/^\s+/gm, '')).join('').trim(); 4 | }; 5 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['ghost'], 4 | extends: [ 5 | 'plugin:ghost/test' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /packages/kg-html-to-lexical/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['ghost'], 4 | extends: [ 5 | 'plugin:ghost/test' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /gh-pages.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Koenig Demo", 3 | "description": "Koenig Demo", 4 | "baseUrl": "/Koenig/", 5 | "repository": { 6 | "url": "https://github.com/TryGhost/Koenig" 7 | } 8 | } -------------------------------------------------------------------------------- /packages/kg-default-atoms/lib/atoms/soft-return.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'soft-return', 3 | type: 'dom', 4 | render(opts) { 5 | return opts.env.dom.createElement('br'); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/SlashMenu.jsx: -------------------------------------------------------------------------------- 1 | export function SlashMenu({children}) { 2 | return ( 3 |
4 | {children} 5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/cards/paywall.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'paywall', 3 | type: 'dom', 4 | 5 | render({env: {dom}}) { 6 | return dom.createComment('members-only'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/kg-converters/lib/kg-converters.js: -------------------------------------------------------------------------------- 1 | import {lexicalToMobiledoc} from './lexical-to-mobiledoc'; 2 | import {mobiledocToLexical} from './mobiledoc-to-lexical'; 3 | 4 | export {lexicalToMobiledoc, mobiledocToLexical}; 5 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss/nesting': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {} 7 | } 8 | }; -------------------------------------------------------------------------------- /packages/koenig-lexical/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss/nesting': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {} 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/cards/HorizontalRuleCard.jsx: -------------------------------------------------------------------------------- 1 | export function HorizontalRuleCard() { 2 | return ( 3 |
4 | ); 5 | } 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getImageFilenameFromSrc.js: -------------------------------------------------------------------------------- 1 | export function getImageFilenameFromSrc(src) { 2 | const url = new URL(src); 3 | const fileName = url.pathname.match(/\/([^/]*)$/)[1]; 4 | return fileName; 5 | } -------------------------------------------------------------------------------- /packages/kg-converters/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ], 6 | parserOptions: { 7 | sourceType: 'module' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ReadOnlyOverlay.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function ReadOnlyOverlay() { 4 | return
; 5 | } 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/openFileSelection.js: -------------------------------------------------------------------------------- 1 | // Triggers the file selection dialog from a given referenced element 2 | 3 | export function openFileSelection({fileInputRef}) { 4 | fileInputRef.current?.click(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/test' 5 | ], 6 | parserOptions: { 7 | sourceType: 'module' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/check-publish-env.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | if (!process.env.VITE_SENTRY_AUTH_TOKEN) { 4 | throw new Error('VITE_SENTRY_AUTH_TOKEN is not set in your .env file - this is required when publishing'); 5 | } 6 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/assets/kg-card-type-unsplash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-align-center.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-align-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-unsplash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-heading-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/index.js: -------------------------------------------------------------------------------- 1 | // publicly exported util functions 2 | export * from './$isAtStartOfDocument'; 3 | export * from './$selectDecoratorNode'; 4 | export * from './$isAtTopOfNode'; 5 | export * from './getTopLevelNativeElement'; 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-divider.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/storybook/populate-storybook-editor.js: -------------------------------------------------------------------------------- 1 | import generateEditorState from '../generateEditorState'; 2 | 3 | export default function populateEditor({editor, initialHtml}) { 4 | generateEditorState({editor, initialHtml}); 5 | } -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-heading-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-italic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@tryghost/kg-default-nodes' { 2 | import {DEFAULT_NODES, ExtendedHeadingNode, ImageNode} from '@tryghost/kg-default-nodes'; 3 | 4 | export {DEFAULT_NODES, ExtendedHeadingNode, ImageNode}; 5 | } 6 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/assets/kg-unsplash-heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-unsplash-heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/nodes/MinimalNodes.js: -------------------------------------------------------------------------------- 1 | import {LinkNode} from '@lexical/link'; 2 | import {TKNode} from '@tryghost/kg-default-nodes'; 3 | 4 | const MINIMAL_NODES = [ 5 | LinkNode, 6 | TKNode 7 | ]; 8 | 9 | export default MINIMAL_NODES; 10 | -------------------------------------------------------------------------------- /packages/koenig-lexical/test/unit/KoenigComposer.test.jsx: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'vitest'; 2 | 3 | describe('KoenigComposer', function () { 4 | it.todo('renders'); 5 | it.todo('accepts initialState prop'); 6 | it.todo('accepts onChange prop'); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-html.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getAccentColor.js: -------------------------------------------------------------------------------- 1 | export function getAccentColor() { 2 | const editor = document.body.querySelector('.koenig-lexical'); 3 | 4 | return (editor && getComputedStyle(editor).getPropertyValue('--kg-accent-color')) || '#ff0095'; 5 | } 6 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/index.ts: -------------------------------------------------------------------------------- 1 | export {UnsplashSearchModal} from './UnsplashSearchModal'; 2 | export type {DefaultHeaderTypes, Photo as PhotoType} from './UnsplashTypes'; 3 | export {UnsplashProvider} from './api/UnsplashProvider'; 4 | import './styles/index.css'; 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-arrow-top-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/assets/kg-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/assets/kg-download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/.storybook/editorEmptyState.js: -------------------------------------------------------------------------------- 1 | export const editorEmptyState = JSON.stringify({"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}); 2 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-email-cta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-indicator-html.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/api/unsplashFixtures.ts: -------------------------------------------------------------------------------- 1 | // purely for testing purposes 2 | import fixturePhotosDataset from './dataFixtures.json'; 3 | 4 | import {Photo} from '../UnsplashTypes'; 5 | 6 | export const fixturePhotos: Photo[] = fixturePhotosDataset as unknown as Photo[]; 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/demo/demo.tsx: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom/client'; 4 | ReactDOM.createRoot(document.getElementById('root')!).render( 5 | 6 | 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-bold.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/tsconfig.declaration.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "declarationDir": "./types" 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-quote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getTopLevelNativeElement.js: -------------------------------------------------------------------------------- 1 | export function getTopLevelNativeElement(node) { 2 | if (node.nodeType === Node.TEXT_NODE) { 3 | node = node.parentNode; 4 | } 5 | 6 | const selector = '[data-lexical-editor] > *'; 7 | return node.closest(selector); 8 | } -------------------------------------------------------------------------------- /packages/kg-utils/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-collection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/kg-card-factory/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts", "package.json"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-quote-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/is-local-content-image.js: -------------------------------------------------------------------------------- 1 | export const isLocalContentImage = function (url, siteUrl = '') { 2 | const normalizedSiteUrl = siteUrl.replace(/\/$/, ''); 3 | const imagePath = url.replace(normalizedSiteUrl, ''); 4 | return /^(\/.*|__GHOST_URL__)\/?content\/images\//.test(imagePath); 5 | }; 6 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/src/kg-default-nodes.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@tryghost/kg-default-nodes' { 2 | import {ElementNode, TextNode} from 'lexical'; 3 | 4 | export class AtLinkNode extends ElementNode {} 5 | export class AtLinkSearchNode extends TextNode {} 6 | export class ZWNJNode extends TextNode {} 7 | } 8 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/utils/is-local-content-image.js: -------------------------------------------------------------------------------- 1 | module.exports = function isLocalContentImage(url, siteUrl = '') { 2 | const normalizedSiteUrl = siteUrl.replace(/\/$/, ''); 3 | const imagePath = url.replace(normalizedSiteUrl, ''); 4 | return /^(\/.*|__GHOST_URL__)\/?content\/images\//.test(imagePath); 5 | }; 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-file-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-snippet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-unmute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "pattern": "./packages/*/" 5 | } 6 | ], 7 | // autofix on save lint 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": "explicit" 10 | }, 11 | "search.exclude": { 12 | "**/node_modules": true, 13 | "**/dist": true} 14 | } 15 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/assets/kg-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-gif.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/$selectDecoratorNode.js: -------------------------------------------------------------------------------- 1 | import { 2 | $createNodeSelection, 3 | $setSelection 4 | } from 'lexical'; 5 | 6 | export function $selectDecoratorNode(node) { 7 | const nodeSelection = $createNodeSelection(); 8 | nodeSelection.add(node.getKey()); 9 | $setSelection(nodeSelection); 10 | } 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-snippet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-video.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/icons/eye-closed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-product.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-img-wide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-img-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/nodes/BasicNodes.js: -------------------------------------------------------------------------------- 1 | import {LinkNode} from '@lexical/link'; 2 | import {ListItemNode, ListNode} from '@lexical/list'; 3 | import {TKNode} from '@tryghost/kg-default-nodes'; 4 | 5 | const BASIC_NODES = [ 6 | ListNode, 7 | ListItemNode, 8 | LinkNode, 9 | TKNode 10 | ]; 11 | 12 | export default BASIC_NODES; 13 | -------------------------------------------------------------------------------- /packages/kg-utils/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-other.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-layout-split.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-eyedropper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-indicator-markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/kg-card-factory/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/icons/eye-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-img-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-shrink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Should Assertions 3 | * 4 | * Add any custom assertions to this file. 5 | */ 6 | 7 | // Example Assertion 8 | // should.Assertion.add('ExampleAssertion', function () { 9 | // this.params = {operator: 'to be a valid Example Assertion'}; 10 | // this.obj.should.be.an.Object; 11 | // }); 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/Navigator.jsx: -------------------------------------------------------------------------------- 1 | import {useNavigate} from 'react-router-dom'; 2 | 3 | const Navigator = () => { 4 | const navigate = useNavigate(); 5 | 6 | // Hack, used to allow Playwright to navigate without triggering a full page reload. 7 | window.navigate = navigate; 8 | 9 | return null; 10 | }; 11 | 12 | export default Navigator; 13 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/KoenigDecoratorNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | /* c8 ignore start */ 3 | import {DecoratorNode} from 'lexical'; 4 | 5 | export class KoenigDecoratorNode extends DecoratorNode { 6 | } 7 | 8 | export function $isKoenigCard(node) { 9 | return node instanceof KoenigDecoratorNode; 10 | } 11 | /* c8 ignore end */ 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-toggle-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/horizontalrule/horizontalrule-parser.js: -------------------------------------------------------------------------------- 1 | export function parseHorizontalRuleNode(HorizontalRuleNode) { 2 | return { 3 | hr: () => ({ 4 | conversion() { 5 | const node = new HorizontalRuleNode(); 6 | return {node}; 7 | }, 8 | priority: 0 9 | }) 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/src/api/IUnsplashProvider.ts: -------------------------------------------------------------------------------- 1 | import {Photo} from '../UnsplashTypes'; 2 | 3 | export interface IUnsplashProvider { 4 | fetchPhotos(): Promise; 5 | fetchNextPage(): Promise; 6 | searchPhotos(term: string): Promise; 7 | triggerDownload(photo: Photo): Promise | void; 8 | searchIsRunning(): boolean; 9 | } 10 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-layout-immersive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/horizontalrule/horizontalrule-renderer.js: -------------------------------------------------------------------------------- 1 | import {addCreateDocumentOption} from '../../utils/add-create-document-option'; 2 | 3 | export function renderHorizontalRuleNode(_, options = {}) { 4 | addCreateDocumentOption(options); 5 | const document = options.createDocument(); 6 | 7 | const element = document.createElement('hr'); 8 | return {element}; 9 | } -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ghost Unsplash Selector 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-gallery-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/shortcutSymbols.js: -------------------------------------------------------------------------------- 1 | function isMac() { 2 | return navigator.userAgent.indexOf('Mac') !== -1; 3 | } 4 | 5 | export function ctrlOrCmdSymbol() { 6 | return isMac() ? '⌘' : 'Ctrl'; 7 | } 8 | 9 | export function ctrlOrSymbol() { 10 | return isMac() ? '⌃' : 'Ctrl'; 11 | } 12 | 13 | export function altOrOption() { 14 | return isMac() ? '⌥' : 'Alt'; 15 | } -------------------------------------------------------------------------------- /.github/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Modified from https://github.com/chaitanyagupta/gitutils 3 | 4 | [ -n "$CI" ] && exit 0 5 | 6 | GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 7 | if [ "$GIT_BRANCH" = "main" ]; then 8 | yarn lint-staged --relative 9 | lintStatus=$? 10 | 11 | if [ $lintStatus -ne 0 ]; then 12 | echo "❌ Linting failed" 13 | exit 1 14 | fi 15 | fi -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/escape-html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Escape HTML special characters 3 | * @param {string} unsafe 4 | * @returns string 5 | */ 6 | export function escapeHtml(unsafe) { 7 | return unsafe 8 | .replace(/&/g, '&') 9 | .replace(//g, '>') 11 | .replace(/"/g, '"') 12 | .replace(/'/g, '''); 13 | } -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node', 5 | 'plugin:ghost/ts', 6 | 'plugin:ghost/ts-test' 7 | ], 8 | // this fouls up with Lexical's need to prefix with '$' for lifecycle hooks 9 | rules: { 10 | 'ghost/filenames/match-exported-class': 'off' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test Utilities 3 | * 4 | * Shared utils for writing tests 5 | */ 6 | 7 | // Require overrides - these add globals for tests 8 | require('./overrides'); 9 | 10 | // Require assertions - adds custom should assertions 11 | require('./assertions'); 12 | 13 | module.exports = { 14 | shouldRender: require('./should-render') 15 | }; 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-file-upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-product-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/EditorPlaceholder.jsx: -------------------------------------------------------------------------------- 1 | export function EditorPlaceholder({className, text}) { 2 | return ( 3 |
{typeof text === 'string' ? text : 'Begin writing your post...'}
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-quote-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/TextInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function TextInput({value, onChange, ...args}) { 4 | const handleOnChange = (e) => { 5 | onChange(e); 6 | }; 7 | 8 | return ( 9 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/prettifyFileName.js: -------------------------------------------------------------------------------- 1 | export default function prettifyFileName(filename) { 2 | if (!filename || typeof filename !== 'string') { 3 | return ''; 4 | } 5 | let updatedName = filename.split('.').slice(0, -1).join('.').replace(/[-_]/g,' ').replace(/[^\w\s]+/g,'').replace(/\s\s+/g, ' '); 6 | return updatedName.charAt(0).toUpperCase() + updatedName.slice(1); 7 | } 8 | -------------------------------------------------------------------------------- /packages/kg-utils/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-audio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/kg-card-factory/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/utils/unsplashConfig.js: -------------------------------------------------------------------------------- 1 | const API_VERSION = 'v1'; 2 | const API_TOKEN = '8672af113b0a8573edae3aa3713886265d9bb741d707f6c01a486cde8c278980'; 3 | 4 | export const defaultHeaders = { 5 | Authorization: `Client-ID ${API_TOKEN}`, 6 | 'Accept-Version': API_VERSION, 7 | 'Content-Type': 'application/json', 8 | 'App-Pragma': 'no-cache', 9 | 'X-Unsplash-Cache': true 10 | }; 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-img-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/plugins/TKCountPlugin.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTKContext} from '../context/TKContext'; 3 | 4 | export default function TKCountPlugin({onChange}) { 5 | const {tkCount} = useTKContext(); 6 | 7 | React.useEffect(() => { 8 | if (!onChange) { 9 | return; 10 | } 11 | 12 | onChange(tkCount); 13 | }, [onChange, tkCount]); 14 | } 15 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/test/test-utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/test/utils/overrides.js: -------------------------------------------------------------------------------- 1 | // This file is required before any test is run 2 | 3 | // Taken from the should wiki, this is how to make should global 4 | // Should is a global in our eslint test config 5 | global.should = require('should').noConflict(); 6 | should.extend(); 7 | 8 | // Sinon is a simple case 9 | // Sinon is a global in our eslint test config 10 | global.sinon = require('sinon'); 11 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/CardVisibilityMessage.jsx: -------------------------------------------------------------------------------- 1 | export function CardVisibilityMessage({message}) { 2 | if (!message) { 3 | return null; 4 | } 5 | 6 | return ( 7 |
8 | {message} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/kg-simplemde/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | "tab" 6 | ], 7 | "strict": 0, 8 | "no-console": 0, 9 | "quotes": [ 10 | 2, 11 | "double" 12 | ], 13 | "semi": [ 14 | 2, 15 | "always" 16 | ], 17 | "no-useless-escape": 0 18 | }, 19 | "env": { 20 | "browser": true, 21 | "node":true 22 | }, 23 | "extends": "eslint:recommended" 24 | } 25 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | playwright-report 27 | test-results 28 | types -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/FileUploadForm.jsx: -------------------------------------------------------------------------------- 1 | export function FileUploadForm({onFileChange, fileInputRef}) { 2 | return ( 3 |
4 | 10 |
11 | ); 12 | } 13 | 14 | export default FileUploadForm; 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/analytics.js: -------------------------------------------------------------------------------- 1 | // Wrapper function for Plausible event 2 | 3 | export default function trackEvent(eventName, props = {}) { 4 | window.plausible = window.plausible || function () { 5 | (window.plausible.q = window.plausible.q || []).push(arguments); 6 | }; 7 | window.plausible(eventName, {props: props}); 8 | if (window.posthog) { 9 | window.posthog.capture(eventName, props); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/slugify.js: -------------------------------------------------------------------------------- 1 | export function slugify(str) { 2 | // Remove HTML tags 3 | str = str.replace(/<[^>]*>?/gm, ''); 4 | 5 | // Remove any non-word character with whitespace 6 | str = str.replace(/[^\w\s]/gi, ''); 7 | 8 | // Replace any whitespace character with a dash 9 | str = str.replace(/\s+/g, '-'); 10 | 11 | // Convert to lowercase 12 | str = str.toLowerCase(); 13 | 14 | return str; 15 | } 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-signup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-img-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/utils/tenorConfig.js: -------------------------------------------------------------------------------- 1 | import {isTestEnv} from '../../test/utils/isTestEnv'; 2 | 3 | export const tenorConfig = isTestEnv ? {googleApiKey: 'xxx'} : getTenorConfig(); 4 | 5 | function getTenorConfig() { 6 | let config = null; 7 | 8 | if (import.meta.env.VITE_TENOR_API_KEY) { 9 | config = { 10 | googleApiKey: import.meta.env.VITE_TENOR_API_KEY 11 | }; 12 | } 13 | 14 | return config; 15 | } 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-replace.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.hbs] 14 | insert_final_newline = false 15 | 16 | [*.json] 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [*.{yml,yaml}] 23 | indent_size = 2 24 | 25 | [Makefile] 26 | indent_style = tab 27 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-layout-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-swap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-video-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/utils/hbs.js: -------------------------------------------------------------------------------- 1 | const Handlebars = require('handlebars'); 2 | 3 | module.exports = function hbs(literals, ...values) { 4 | // interweave strings with substitutions 5 | let output = ''; 6 | for (let i = 0; i < values.length; i++) { 7 | output += literals[i] + values[i]; 8 | } 9 | output += literals[values.length]; 10 | 11 | // return compiled handlebars template 12 | return Handlebars.compile(output); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/DarkModeToggle.jsx: -------------------------------------------------------------------------------- 1 | const DarkModeToggle = ({darkMode, toggleDarkMode}) => { 2 | return ( 3 | <> 4 | 7 | 8 | ); 9 | }; 10 | 11 | export default DarkModeToggle; 12 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getEditorCardNodes.js: -------------------------------------------------------------------------------- 1 | export function getEditorCardNodes(editor) { 2 | // TODO: open upstream PR to add public method of getting nodes 3 | const allNodes = editor._nodes; 4 | const cardNodes = []; 5 | 6 | for (const [nodeType, {klass}] of allNodes) { 7 | if (!klass.kgMenu) { 8 | continue; 9 | } 10 | 11 | cardNodes.push([nodeType, klass]); 12 | } 13 | 14 | return cardNodes; 15 | } 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/svgo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | multipass: true, 3 | plugins: [ 4 | { 5 | name: 'preset-default', 6 | params: { 7 | overrides: { 8 | // viewBox is required to resize SVGs with CSS. 9 | // @see https://github.com/svg/svgo/issues/1128 10 | removeViewBox: false 11 | } 12 | } 13 | } 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/isInternalUrl.js: -------------------------------------------------------------------------------- 1 | export function isInternalUrl(url, siteUrl) { 2 | if (!url || !siteUrl) { 3 | return false; 4 | } 5 | 6 | try { 7 | const urlObj = new URL(url); 8 | const subdir = `/${new URL(siteUrl).pathname.split('/')[1]}`; 9 | return urlObj.hostname === new URL(siteUrl).hostname 10 | && urlObj.pathname.startsWith(subdir); 11 | } catch (e) { 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | storybook-static 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | /test-results/ 26 | /playwright-report/ 27 | /playwright/.cache/ 28 | .env 29 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/assets/icons/kg-lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/autoExpandTextArea.js: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | const useAutoExpandTextArea = ({el, value}) => { 4 | useEffect(() => { 5 | const element = el.current; 6 | if (element) { 7 | element.style.height = '0px'; 8 | const height = element.scrollHeight; 9 | element.style.height = `${height}px`; 10 | } 11 | }, [el, value]); 12 | }; 13 | 14 | export default useAutoExpandTextArea; 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getAudioMetadata.js: -------------------------------------------------------------------------------- 1 | // gets image dimensions from a given Url 2 | 3 | export async function getAudioMetadata(url) { 4 | let audio = new Audio(); 5 | let duration; 6 | 7 | return new Promise((resolve) => { 8 | audio.onloadedmetadata = function () { 9 | duration = audio.duration; 10 | resolve({ 11 | duration 12 | }); 13 | }; 14 | audio.src = url; 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/plugins/CardMenuPlugin.jsx: -------------------------------------------------------------------------------- 1 | import PlusCardMenuPlugin from '../plugins/PlusCardMenuPlugin'; 2 | import React from 'react'; 3 | import SlashCardMenuPlugin from '../plugins/SlashCardMenuPlugin'; 4 | 5 | export const CardMenuPlugin = () => { 6 | return ( 7 | <> 8 | {/* Koenig Plugins */} 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default CardMenuPlugin; 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "yarn", 4 | "packages": ["packages/*"], 5 | "command": { 6 | "version": { 7 | "exact": true 8 | }, 9 | "publish": { 10 | "allowBranch": "main", 11 | "message": "Published new versions" 12 | } 13 | }, 14 | "local": { 15 | "public": "true", 16 | "repo": "https://github.com/TryGhost/Koenig", 17 | "scope": "@tryghost" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-layout-minimal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dedent: require('./dedent'), 3 | getAvailableImageWidths: require('./get-available-image-widths'), 4 | hbs: require('./hbs'), 5 | isLocalContentImage: require('./is-local-content-image'), 6 | isUnsplashImage: require('./is-unsplash-image'), 7 | resizeImage: require('./resize-image'), 8 | generateImgAttrs: require('./generate-img-attrs'), 9 | ...require('./srcset-attribute') // get/setSrcsetAttribute 10 | }; 11 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/at-link/kg-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/context/SharedOnChangeContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Context = React.createContext({}); 4 | 5 | export const SharedOnChangeContext = ({onChange, children}) => { 6 | const onChangeContext = React.useMemo( 7 | () => ({onChange}), 8 | [onChange] 9 | ); 10 | 11 | return {children}; 12 | }; 13 | 14 | export const useSharedOnChangeContext = () => React.useContext(Context); 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Koenig Lexical Demo 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/lib/cards/soft-return.js: -------------------------------------------------------------------------------- 1 | export function fromBr() { 2 | // mobiledoc by default ignores
tags but we have a custom SoftReturn atom 3 | return function fromBrToSoftReturnAtom(node, builder, {addMarkerable, nodeFinished}) { 4 | if (node.nodeType !== 1 || node.tagName !== 'BR') { 5 | return; 6 | } 7 | 8 | let softReturn = builder.createAtom('soft-return'); 9 | addMarkerable(softReturn); 10 | 11 | nodeFinished(); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/AudioUploadForm.jsx: -------------------------------------------------------------------------------- 1 | export function AudioUploadForm({onFileChange, fileInputRef, mimeTypes = ['audio/*']}) { 2 | return ( 3 |
4 | 11 |
12 | ); 13 | } 14 | 15 | export default AudioUploadForm; 16 | -------------------------------------------------------------------------------- /packages/kg-converters/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ], 6 | parser: '@babel/eslint-parser', 7 | parserOptions: { 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | '@babel/plugin-syntax-import-assertions' 13 | ] 14 | } 15 | }, 16 | env: { 17 | browser: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/Input.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Input} from './Input'; 4 | 5 | const story = { 6 | title: 'Generic/Input', 7 | component: Input, 8 | parameters: { 9 | status: { 10 | type: 'uiReady' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = args => ( 17 |
18 | 19 |
20 | ); 21 | 22 | export const Default = Template.bind({}); 23 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ], 6 | parser: '@babel/eslint-parser', 7 | parserOptions: { 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | '@babel/plugin-syntax-import-assertions' 13 | ] 14 | } 15 | }, 16 | env: { 17 | browser: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ], 6 | parser: '@babel/eslint-parser', 7 | parserOptions: { 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | '@babel/plugin-syntax-import-assertions' 13 | ] 14 | } 15 | }, 16 | env: { 17 | browser: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/kg-parser-plugins/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['ghost'], 3 | extends: [ 4 | 'plugin:ghost/node' 5 | ], 6 | parser: '@babel/eslint-parser', 7 | parserOptions: { 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | '@babel/plugin-syntax-import-assertions' 13 | ] 14 | } 15 | }, 16 | env: { 17 | browser: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/Toggle.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Toggle} from './Toggle'; 4 | 5 | const story = { 6 | title: 'Generic/Toggle', 7 | component: Toggle, 8 | parameters: { 9 | status: { 10 | type: 'functional' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = args => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.args = { 22 | isChecked: true 23 | }; 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/styles/components/react-colorful.css: -------------------------------------------------------------------------------- 1 | .koenig-lexical .react-colorful { 2 | width: 100%; 3 | height: 140px; 4 | 5 | .react-colorful__saturation { 6 | border-radius: 4px 4px 0 0; 7 | border-bottom: 1px solid #000; 8 | } 9 | 10 | .react-colorful__hue { 11 | height: 8px; 12 | border-radius: 0 0 4px 4px; 13 | } 14 | 15 | .react-colorful__pointer { 16 | width: 14px; 17 | height: 14px; 18 | border-width: 1px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-callout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getImageDimensions.js: -------------------------------------------------------------------------------- 1 | // gets image dimensions from a given Url 2 | 3 | export async function getImageDimensions(url) { 4 | const img = new Image(); 5 | return new Promise((resolve, reject) => { 6 | img.onload = () => { 7 | resolve({width: img.naturalWidth, height: img.naturalHeight}); 8 | }; 9 | img.onerror = reject; 10 | // Set image src after listeners to avoid the image loading before the listener is set 11 | img.src = url; 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-gen-embed.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getScrollParent.js: -------------------------------------------------------------------------------- 1 | export function getScrollParent(node) { 2 | const isElement = node instanceof HTMLElement; 3 | const overflowY = isElement && window.getComputedStyle(node).overflowY; 4 | const isScrollable = overflowY !== 'visible' && overflowY !== 'hidden'; 5 | 6 | if (!node) { 7 | return null; 8 | } else if (isScrollable && node.scrollHeight >= node.clientHeight) { 9 | return node; 10 | } 11 | 12 | return getScrollParent(node.parentNode) || document.body; 13 | } 14 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-wand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/callToActionColors.js: -------------------------------------------------------------------------------- 1 | export const CALLTOACTION_COLORS = { 2 | none: 'bg-transparent border-transparent', 3 | white: 'bg-transparent border-grey-900/15 dark:border-grey-100/20', 4 | grey: 'bg-grey/10 border-transparent', 5 | blue: 'bg-blue/10 border-transparent', 6 | green: 'bg-green/10 border-transparent', 7 | yellow: 'bg-yellow/10 border-transparent', 8 | red: 'bg-red/10 border-transparent', 9 | pink: 'bg-pink/10 border-transparent', 10 | purple: 'bg-purple/10 border-transparent' 11 | }; -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/transformers/element/paragraph.ts: -------------------------------------------------------------------------------- 1 | import {$isParagraphNode} from 'lexical'; 2 | import type {ElementNode} from 'lexical'; 3 | import type {ExportChildren} from '..'; 4 | import type {RendererOptions} from '@tryghost/kg-default-nodes'; 5 | 6 | module.exports = { 7 | export(node: ElementNode, options: RendererOptions, exportChildren: ExportChildren) { 8 | if (!$isParagraphNode(node)) { 9 | return null; 10 | } 11 | 12 | return `

${exportChildren(node)}

`; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/MediaPlayer.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {MediaPlayer} from './MediaPlayer'; 4 | 5 | const story = { 6 | title: 'Generic/Media player', 7 | component: MediaPlayer, 8 | parameters: { 9 | status: { 10 | type: 'functional' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = args => ( 17 | 18 | ); 19 | 20 | export const Default = Template.bind({}); 21 | Default.args = { 22 | theme: 'dark' 23 | }; -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-sync.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/isEditorEmpty.js: -------------------------------------------------------------------------------- 1 | import {$canShowPlaceholderCurry} from '@lexical/text'; 2 | 3 | export function isEditorEmpty(editor) { 4 | // NOTE: This feels hacky but was required because we check editor empty state 5 | // when rendering cards to determine whether to show nested editors. But 6 | // _after an undo_ at the point we check the nested editor state is not yet fully committed 7 | const editorState = editor._pendingEditorState || editor.getEditorState(); 8 | return editorState.read($canShowPlaceholderCurry(false)); 9 | } 10 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-audio-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ActionToolbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useKoenigSelectedCardContext} from '../../context/KoenigSelectedCardContext'; 3 | 4 | export function ActionToolbar({isVisible, children, ...props}) { 5 | const {isDragging} = useKoenigSelectedCardContext(); 6 | 7 | if (isVisible && !isDragging) { 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/PlusMenu.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {PlusButton} from './PlusMenu'; 4 | 5 | const story = { 6 | title: 'Card menu/Plus button', 7 | component: PlusButton, 8 | parameters: { 9 | status: { 10 | type: 'functional' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = args => ( 17 |
18 | 19 |
20 | ); 21 | 22 | export const Default = Template.bind({}); 23 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/Slider.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Slider} from './Slider'; 3 | 4 | const story = { 5 | title: 'Generic/Slider', 6 | component: Slider, 7 | parameters: { 8 | status: { 9 | type: 'functional' 10 | } 11 | } 12 | }; 13 | export default story; 14 | 15 | const Template = args => ( 16 | 17 | ); 18 | 19 | export const Default = Template.bind({}); 20 | Default.args = { 21 | min: 1, 22 | max: 10, 23 | value: 5, 24 | onChange: () => {} 25 | }; -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/SubscribeForm.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {SubscribeForm} from './SubscribeForm'; 4 | 5 | const story = { 6 | title: 'Generic/Subscribe form', 7 | component: SubscribeForm, 8 | parameters: { 9 | status: { 10 | type: 'uiReady' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = args => ( 17 |
18 | 19 |
20 | ); 21 | 22 | export const Default = Template.bind({}); 23 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/file-selectors/UnsplashModal.jsx: -------------------------------------------------------------------------------- 1 | import Portal from '../Portal'; 2 | import {UnsplashSearchModal} from '@tryghost/kg-unsplash-selector'; 3 | 4 | const UnsplashModal = ({unsplashConf, onImageInsert, onClose}) => { 5 | return ( 6 | 7 | 12 | 13 | ); 14 | }; 15 | 16 | export default UnsplashModal; 17 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ProgressBar.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ProgressBar} from './ProgressBar'; 3 | 4 | const story = { 5 | title: 'Generic/Progress bar', 6 | component: ProgressBar, 7 | parameters: { 8 | status: { 9 | type: 'functional' 10 | } 11 | } 12 | }; 13 | export default story; 14 | 15 | const Template = args => ( 16 | 17 | ); 18 | 19 | export const Default = Template.bind({}); 20 | Default.args = { 21 | style: {width: 60 + '%'}, 22 | fullWidth: false 23 | }; -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/paywall/paywall-renderer.js: -------------------------------------------------------------------------------- 1 | import {addCreateDocumentOption} from '../../utils/add-create-document-option'; 2 | 3 | export function renderPaywallNode(_, options = {}) { 4 | addCreateDocumentOption(options); 5 | const document = options.createDocument(); 6 | const element = document.createElement('div'); 7 | 8 | element.innerHTML = ''; 9 | 10 | // `type: 'inner'` will render only the innerHTML of the element 11 | // @see @tryghost/kg-lexical-html-renderer package 12 | return {element, type: 'inner'}; 13 | } 14 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/clean-dom.js: -------------------------------------------------------------------------------- 1 | export function cleanDOM(node, allowedTags) { 2 | for (let i = 0; i < node.childNodes.length; i++) { 3 | let child = node.childNodes[i]; 4 | if (child.nodeType === 1 && !allowedTags.includes(child.tagName)) { 5 | while (child.firstChild) { 6 | node.insertBefore(child.firstChild, child); 7 | } 8 | node.removeChild(child); 9 | i -= 1; 10 | } else if (child.nodeType === 1) { 11 | cleanDOM(child, allowedTags); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/paywall/paywall-parser.js: -------------------------------------------------------------------------------- 1 | export function parsePaywallNode(PaywallNode) { 2 | return { 3 | '#comment': (nodeElem) => { 4 | if (nodeElem.nodeType === 8 && nodeElem.nodeValue.trim() === 'members-only') { 5 | return { 6 | conversion() { 7 | const node = new PaywallNode(); 8 | return {node}; 9 | }, 10 | priority: 0 11 | }; 12 | } 13 | return null; 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/context/SharedHistoryContext.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createEmptyHistoryState} from '@lexical/react/LexicalHistoryPlugin'; 3 | 4 | const Context = React.createContext({}); 5 | 6 | export const SharedHistoryContext = ({children}) => { 7 | const historyContext = React.useMemo( 8 | () => ({historyState: createEmptyHistoryState()}), 9 | [] 10 | ); 11 | 12 | return {children}; 13 | }; 14 | 15 | export const useSharedHistoryContext = () => React.useContext(Context); 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/Delayed.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function Delayed({children, waitBeforeShow = 500}) { 4 | const [show, setShow] = React.useState(waitBeforeShow === 0); 5 | 6 | React.useEffect(() => { 7 | if (show) { 8 | return; 9 | } 10 | 11 | const timeout = setTimeout(() => { 12 | setShow(true); 13 | }, waitBeforeShow); 14 | 15 | return () => { 16 | clearTimeout(timeout); 17 | }; 18 | }, [show, waitBeforeShow]); 19 | 20 | return show ? children : null; 21 | } 22 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-audio-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/kg-simplemde/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplemde", 3 | "version": "1.11.2", 4 | "homepage": "https://github.com/NextStepWebs/simplemde-markdown-editor", 5 | "authors": [ 6 | "Wes Cossick " 7 | ], 8 | "description": "A simple, beautiful, and embeddable JavaScript Markdown editor.", 9 | "main": ["src/js/simplemde.js", "src/css/simplemde.css"], 10 | "keywords": [ 11 | "embeddable", 12 | "markdown", 13 | "editor", 14 | "javascript", 15 | "wysiwyg" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-indicator-email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-layout-grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/plugins/KoenigBlurPlugin.jsx: -------------------------------------------------------------------------------- 1 | import {BLUR_COMMAND, COMMAND_PRIORITY_EDITOR} from 'lexical'; 2 | import {useEffect} from 'react'; 3 | import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; 4 | 5 | export const KoenigBlurPlugin = ({onBlur}) => { 6 | const [editor] = useLexicalComposerContext(); 7 | useEffect(() => { 8 | editor.registerCommand( 9 | BLUR_COMMAND, 10 | () => { 11 | onBlur?.(); 12 | }, 13 | COMMAND_PRIORITY_EDITOR 14 | ); 15 | }, [editor, onBlur]); 16 | 17 | return null; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/plugins/KoenigFocusPlugin.jsx: -------------------------------------------------------------------------------- 1 | import {COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND} from 'lexical'; 2 | import {useEffect} from 'react'; 3 | import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; 4 | 5 | export const KoenigFocusPlugin = ({onFocus}) => { 6 | const [editor] = useLexicalComposerContext(); 7 | useEffect(() => { 8 | editor.registerCommand( 9 | FOCUS_COMMAND, 10 | () => { 11 | onFocus?.(); 12 | }, 13 | COMMAND_PRIORITY_EDITOR 14 | ); 15 | }, [editor, onFocus]); 16 | 17 | return null; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/serializers/paragraph.js: -------------------------------------------------------------------------------- 1 | export default { 2 | import: { 3 | p: (node) => { 4 | const isGoogleDocs = !!node.closest('[id^="docs-internal-guid-"]'); 5 | 6 | // Google docs wraps dividers in paragraphs, without text content 7 | // Remove them to avoid creating empty paragraphs in the editor 8 | if (isGoogleDocs && node.textContent === '') { 9 | return { 10 | conversion: () => null, 11 | priority: 1 12 | }; 13 | } 14 | 15 | return null; 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/KoenigErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | import KoenigComposerContext from '../context/KoenigComposerContext'; 2 | import React from 'react'; 3 | import {ErrorBoundary as ReactErrorBoundary} from 'react-error-boundary'; 4 | 5 | export default function KoenigErrorBoundary({children}) { 6 | const {onError} = React.useContext(KoenigComposerContext); 7 | 8 | return ( 9 | An error was thrown.} 11 | onError={onError} 12 | > 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/cards/PaywallCard.jsx: -------------------------------------------------------------------------------- 1 | export function PaywallCard() { 2 | return ( 3 |
4 | Free public preview 5 | 6 | / 7 | 8 | Only visible to members 9 |
10 | ); 11 | } -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/cards/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./bookmark'), 3 | require('./code'), 4 | require('./email'), 5 | require('./email-cta'), 6 | require('./embed'), 7 | require('./gallery'), 8 | require('./hr'), 9 | require('./html'), 10 | require('./image'), 11 | require('./markdown'), 12 | require('./paywall'), 13 | require('./button'), 14 | require('./callout'), 15 | require('./product'), 16 | require('./toggle'), 17 | require('./audio'), 18 | require('./video'), 19 | require('./file'), 20 | require('./header'), 21 | require('./before-after') 22 | ]; 23 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/WordCount.jsx: -------------------------------------------------------------------------------- 1 | const WordCount = ({wordCount, tkCount}) => { 2 | return ( 3 |
4 | {wordCount} words 5 | {tkCount > 0 && ( 6 | <> 7 | {' '} 8 | / {tkCount} TK{tkCount > 1 && 's'} 9 | 10 | )} 11 |
12 | ); 13 | }; 14 | 15 | export default WordCount; 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-eye-closed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/IconButton.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import DeleteIcon from '../../assets/icons/kg-trash.svg?react'; 4 | import {IconButton} from './IconButton'; 5 | 6 | const story = { 7 | title: 'Generic/Icon button', 8 | component: IconButton, 9 | parameters: { 10 | status: { 11 | type: 'functional' 12 | } 13 | } 14 | }; 15 | export default story; 16 | 17 | const Template = (args) => { 18 | return ( 19 | 20 | ); 21 | }; 22 | 23 | export const Default = Template.bind({}); 24 | Default.args = { 25 | Icon: DeleteIcon 26 | }; 27 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getSelectedNode.js: -------------------------------------------------------------------------------- 1 | import {$isAtNodeEnd} from '@lexical/selection'; 2 | 3 | export function getSelectedNode(selection) { 4 | const anchor = selection.anchor; 5 | const focus = selection.focus; 6 | const anchorNode = selection.anchor.getNode(); 7 | const focusNode = selection.focus.getNode(); 8 | if (anchorNode === focusNode) { 9 | return anchorNode; 10 | } 11 | const isBackward = selection.isBackward(); 12 | if (isBackward) { 13 | return $isAtNodeEnd(focus) ? anchorNode : focusNode; 14 | } else { 15 | return $isAtNodeEnd(anchor) ? focusNode : anchorNode; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/cards/hr.test.js: -------------------------------------------------------------------------------- 1 | // Switch these lines once there are useful utils 2 | // const testUtils = require('./utils'); 3 | require('../utils'); 4 | 5 | const card = require('../../lib/cards/hr'); 6 | const SimpleDom = require('simple-dom'); 7 | const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap); 8 | 9 | describe('HR card', function () { 10 | it('generates a horizontal rule', function () { 11 | let opts = { 12 | env: { 13 | dom: new SimpleDom.Document() 14 | } 15 | }; 16 | 17 | serializer.serialize(card.render(opts)).should.match('
'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/build-clean-basic-html-for-element.js: -------------------------------------------------------------------------------- 1 | import cleanBasicHtml from '@tryghost/kg-clean-basic-html'; 2 | 3 | export function buildCleanBasicHtmlForElement(domNode) { 4 | return function _cleanBasicHtml(html, additionalOptions = {}) { 5 | const cleanedHtml = cleanBasicHtml(html, { 6 | createDocument: (_html) => { 7 | const newDoc = domNode.ownerDocument.implementation.createHTMLDocument(); 8 | newDoc.body.innerHTML = _html; 9 | return newDoc; 10 | }, 11 | ...additionalOptions 12 | }); 13 | return cleanedHtml; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/email/EmailNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {renderEmailNode} from './email-renderer'; 4 | 5 | export class EmailNode extends generateDecoratorNode({ 6 | nodeType: 'email', 7 | properties: [ 8 | {name: 'html', default: '', urlType: 'html'} 9 | ], 10 | defaultRenderFn: renderEmailNode 11 | }) { 12 | } 13 | 14 | export const $createEmailNode = (dataset) => { 15 | return new EmailNode(dataset); 16 | }; 17 | 18 | export function $isEmailNode(node) { 19 | return node instanceof EmailNode; 20 | } 21 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/lib/utils/is-unsplash-image.test.js: -------------------------------------------------------------------------------- 1 | // Switch these lines once there are useful utils 2 | // const testUtils = require('./utils'); 3 | require('../../utils'); 4 | 5 | const isUnsplashImage = require('../../../lib/utils/is-unsplash-image'); 6 | 7 | describe('Utils: isUnsplashImage', function () { 8 | it('returns true when url matches unsplash url', function () { 9 | isUnsplashImage('https://images.unsplash.com/test').should.be.true(); 10 | }); 11 | 12 | it('returns false when url does not match unsplash url', function () { 13 | isUnsplashImage('https://images.example.com/test').should.be.false(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ProgressBar.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | export function ProgressBar({style, fullWidth, bgStyle}) { 5 | return ( 6 |
7 |
8 |
9 | ); 10 | } 11 | 12 | ProgressBar.propTypes = { 13 | style: PropTypes.object, 14 | fullWidth: PropTypes.bool 15 | }; 16 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/hooks/useClickOutside.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function useClickOutside(enabled, ref, handler) { 4 | React.useEffect(() => { 5 | if (!enabled) { 6 | return; 7 | } 8 | 9 | const handleClickOutside = (event) => { 10 | if (ref.current && !ref.current.contains(event.target)) { 11 | handler(); 12 | } 13 | }; 14 | 15 | window.addEventListener('mousedown', handleClickOutside, {capture: true}); 16 | return () => window.removeEventListener('mousedown', handleClickOutside, {capture: true}); 17 | }, [enabled, handler, ref]); 18 | } 19 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/test/atoms/soft-return.test.js: -------------------------------------------------------------------------------- 1 | // Switch these lines once there are useful utils 2 | // const testUtils = require('../utils'); 3 | require('../utils'); 4 | 5 | const atom = require('../../lib/atoms/soft-return'); 6 | const SimpleDom = require('simple-dom'); 7 | const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap); 8 | 9 | describe('Soft return atom', function () { 10 | it('generates a `br` tag', function () { 11 | let opts = { 12 | env: { 13 | dom: new SimpleDom.Document() 14 | } 15 | }; 16 | 17 | serializer.serialize(atom.render(opts)).should.match('
'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/thumbnailUploadHandler.js: -------------------------------------------------------------------------------- 1 | import {$getNodeByKey} from 'lexical'; 2 | 3 | export const thumbnailUploadHandler = async (files, nodeKey, editor, upload) => { 4 | if (!files) { 5 | return; 6 | } 7 | 8 | let mediaSrc = ''; 9 | 10 | editor.getEditorState().read(() => { 11 | const node = $getNodeByKey(nodeKey); 12 | mediaSrc = node.src; 13 | }); 14 | 15 | const uploadResult = await upload(files, {formData: {url: mediaSrc}}); 16 | 17 | await editor.update(() => { 18 | const node = $getNodeByKey(nodeKey); 19 | node.thumbnailSrc = uploadResult[0].url; 20 | }); 21 | 22 | return; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/kg-default-cards/lib/utils/resize-image.js: -------------------------------------------------------------------------------- 1 | module.exports = function resizeImage(image, {width: desiredWidth, height: desiredHeight} = {}) { 2 | const {width, height} = image; 3 | const ratio = width / height; 4 | 5 | if (desiredWidth) { 6 | const resizedHeight = Math.round(desiredWidth / ratio); 7 | 8 | return { 9 | width: desiredWidth, 10 | height: resizedHeight 11 | }; 12 | } 13 | 14 | if (desiredHeight) { 15 | const resizedWidth = Math.round(desiredHeight * ratio); 16 | 17 | return { 18 | width: resizedWidth, 19 | height: desiredHeight 20 | }; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/Tooltip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function Tooltip({label, shortcutKeys}) { 4 | return ( 5 |
6 | {label} 7 | {shortcutKeys && shortcutKeys.map(k => ( 8 |
{k}
9 | ))} 10 |
11 | ); 12 | } -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/LinkToolbar.stories.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-key */ 2 | import React from 'react'; 3 | 4 | import {LinkToolbar} from './LinkToolbar'; 5 | 6 | const story = { 7 | title: 'Toolbar/LinkToolbar', 8 | component: LinkToolbar, 9 | parameters: { 10 | status: { 11 | type: 'functional' 12 | } 13 | } 14 | }; 15 | export default story; 16 | 17 | const Template = (args) => { 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | 25 | export const Base = Template.bind({}); 26 | Base.args = { 27 | href: 'https://ghost.org/' 28 | }; 29 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/read-caption-from-element.js: -------------------------------------------------------------------------------- 1 | import {buildCleanBasicHtmlForElement} from './build-clean-basic-html-for-element'; 2 | 3 | export function readCaptionFromElement(element, {selector = 'figcaption'} = {}) { 4 | const cleanBasicHtml = buildCleanBasicHtmlForElement(element); 5 | 6 | let caption; 7 | 8 | const figcaptions = Array.from(element.querySelectorAll(selector)); 9 | if (figcaptions.length) { 10 | figcaptions.forEach((figcaption) => { 11 | const cleanHtml = cleanBasicHtml(figcaption.innerHTML); 12 | caption = caption ? `${caption} / ${cleanHtml}` : cleanHtml; 13 | }); 14 | } 15 | 16 | return caption; 17 | } 18 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/render-empty-container.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Renders an empty container element 3 | * In the returned object, `type: 'inner'` is picked up by the `@tryghost/kg-lexical-html-renderer` package 4 | * to render the inner content of the container element (in this case, nothing) 5 | * 6 | * @see @tryghost/kg-lexical-html-renderer package 7 | * @see https://github.com/TryGhost/Koenig/blob/e14c008e176f7a1036fe3f3deb924ed69a69191f/packages/kg-lexical-html-renderer/lib/convert-to-html-string.js#L29 8 | */ 9 | export function renderEmptyContainer(document) { 10 | const emptyContainer = document.createElement('span'); 11 | return {element: emptyContainer, type: 'inner'}; 12 | } 13 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/src/transforms/remove-alignment.ts: -------------------------------------------------------------------------------- 1 | import {ElementNode, Klass, LexicalEditor} from 'lexical'; 2 | 3 | export function removeAlignmentTransform(node: ElementNode) { 4 | // on element nodes format===text-align in Lexical 5 | if (node.getFormatType() !== '') { 6 | node.setFormat(''); 7 | } 8 | } 9 | 10 | export function registerRemoveAlignmentTransform(editor: LexicalEditor, klass: Klass) { 11 | if (editor.hasNodes([klass])) { 12 | return editor.registerNodeTransform(klass, removeAlignmentTransform); 13 | } 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-empty-function 16 | return () => {}; 17 | } 18 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/transformers/index.ts: -------------------------------------------------------------------------------- 1 | import {ElementNode} from 'lexical'; 2 | import type {RendererOptions} from '@tryghost/kg-default-nodes'; 3 | 4 | export type ExportChildren = (node: ElementNode, options?: RendererOptions) => string; 5 | export type ElementTransformer = { 6 | export: (node: ElementNode, options: RendererOptions, exportChildren: ExportChildren) => string | null; 7 | }; 8 | 9 | const elementTransformers: ElementTransformer[] = [ 10 | require('./element/paragraph'), 11 | require('./element/heading'), 12 | require('./element/list'), 13 | require('./element/blockquote'), 14 | require('./element/aside') 15 | ]; 16 | 17 | export default elementTransformers; 18 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-gallery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/get-resized-image-dimensions.js: -------------------------------------------------------------------------------- 1 | export const getResizedImageDimensions = function (image, {width: desiredWidth, height: desiredHeight} = {}) { 2 | const {width, height} = image; 3 | const ratio = width / height; 4 | 5 | if (desiredWidth) { 6 | const resizedHeight = Math.round(desiredWidth / ratio); 7 | 8 | return { 9 | width: desiredWidth, 10 | height: resizedHeight 11 | }; 12 | } 13 | 14 | if (desiredHeight) { 15 | const resizedWidth = Math.round(desiredHeight * ratio); 16 | 17 | return { 18 | width: resizedWidth, 19 | height: desiredHeight 20 | }; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-upload-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ImageUploadForm.jsx: -------------------------------------------------------------------------------- 1 | export function ImageUploadForm({onFileChange, fileInputRef, mimeTypes = ['image/*'], multiple = false, disabled}) { 2 | const accept = mimeTypes.join(','); 3 | 4 | return ( 5 |
6 | e.stopPropagation()} 15 | /> 16 |
17 | ); 18 | } 19 | 20 | export default ImageUploadForm; 21 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/test/utils/should-render.js: -------------------------------------------------------------------------------- 1 | const jsdom = require('jsdom'); 2 | const {JSDOM} = jsdom; 3 | const Renderer = require('../../'); 4 | 5 | const dom = new JSDOM(); 6 | 7 | function shouldRender({input, output, options = {}}) { 8 | return async function () { 9 | const defaultOnError = (err) => { 10 | throw err; 11 | }; 12 | 13 | const {nodes, onError, ...renderOptions} = options; 14 | const renderer = new Renderer({dom, nodes, onError: onError || defaultOnError}); 15 | const renderedInput = await renderer.render(input, renderOptions); 16 | renderedInput.should.equal(output); 17 | }; 18 | } 19 | 20 | module.exports = shouldRender; 21 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/markdown/markdown-renderer.js: -------------------------------------------------------------------------------- 1 | import markdownHtmlRenderer from '@tryghost/kg-markdown-html-renderer'; 2 | import {addCreateDocumentOption} from '../../utils/add-create-document-option'; 3 | 4 | export function renderMarkdownNode(node, options = {}) { 5 | addCreateDocumentOption(options); 6 | const document = options.createDocument(); 7 | 8 | const html = markdownHtmlRenderer.render(node.markdown || '', options); 9 | 10 | const element = document.createElement('div'); 11 | element.innerHTML = html; 12 | 13 | // `type: 'inner'` will render only the innerHTML of the element 14 | // @see @tryghost/kg-lexical-html-renderer package 15 | return {element, type: 'inner'}; 16 | } 17 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/paywall/PaywallNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {parsePaywallNode} from './paywall-parser'; 4 | import {renderPaywallNode} from './paywall-renderer'; 5 | 6 | export class PaywallNode extends generateDecoratorNode({ 7 | nodeType: 'paywall', 8 | defaultRenderFn: renderPaywallNode 9 | }) { 10 | static importDOM() { 11 | return parsePaywallNode(this); 12 | } 13 | } 14 | 15 | export const $createPaywallNode = (dataset) => { 16 | return new PaywallNode(dataset); 17 | }; 18 | 19 | export function $isPaywallNode(node) { 20 | return node instanceof PaywallNode; 21 | } 22 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/tagged-template-fns.mjs: -------------------------------------------------------------------------------- 1 | const oneline = function (strings, ...values) { 2 | // Handle case where a plain string is passed 3 | if (typeof strings === 'string') { 4 | return strings.replace(/\n\s+/g, '').trim(); 5 | } 6 | 7 | // Handle tagged template literal case 8 | const result = strings.reduce((acc, str, i) => { 9 | return acc + str + (values[i] || ''); 10 | }, ''); 11 | // Remove newline+indentation patterns while preserving intentional whitespace 12 | return result.replace(/\n\s+/g, '').trim(); 13 | }; 14 | 15 | // Using `html` as a synonym for `oneline` in order to get syntax highlighting in editors 16 | const html = oneline; 17 | 18 | export {oneline, html}; -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/transformers/element/heading.ts: -------------------------------------------------------------------------------- 1 | import {$isHeadingNode} from '@lexical/rich-text'; 2 | import generateId from '../../utils/generate-id'; 3 | import type {RendererOptions} from '@tryghost/kg-default-nodes'; 4 | import type {ElementNode} from 'lexical'; 5 | import type {ExportChildren} from '..'; 6 | 7 | module.exports = { 8 | export(node: ElementNode, options: RendererOptions, exportChildren: ExportChildren) { 9 | if (!$isHeadingNode(node)) { 10 | return null; 11 | } 12 | 13 | const tag = node.getTag(); 14 | const id = generateId(node.getTextContent(), options); 15 | 16 | return `<${tag} id="${id}">${exportChildren(node)}`; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/kg-utils/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Utils 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-utils --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-utils` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/LinkInput.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {LinkInput} from './LinkInput'; 4 | 5 | const story = { 6 | title: 'Toolbar/LinkInput', 7 | component: LinkInput, 8 | parameters: { 9 | status: { 10 | type: 'functional' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = (args) => { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | }; 23 | 24 | export const Empty = Template.bind({}); 25 | Empty.args = { 26 | href: '' 27 | }; 28 | 29 | export const Populated = Template.bind({}); 30 | Populated.args = { 31 | href: 'https://ghost.org' 32 | }; -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/utils/generate-id.ts: -------------------------------------------------------------------------------- 1 | import type {RendererOptions} from '@tryghost/kg-default-nodes'; 2 | import {slugify} from '@tryghost/kg-utils'; 3 | 4 | function generateId(text: string, options: RendererOptions) { 5 | if (!options.usedIdAttributes) { 6 | options.usedIdAttributes = {}; 7 | } 8 | 9 | const id = slugify(text, options); 10 | let deduplicatedId = id; 11 | 12 | if (options.usedIdAttributes[id] !== undefined) { 13 | deduplicatedId += `-${options.usedIdAttributes[id]}`; 14 | 15 | options.usedIdAttributes[id] += 1; 16 | } else { 17 | options.usedIdAttributes[id] = 1; 18 | } 19 | 20 | return deduplicatedId; 21 | } 22 | 23 | export default generateId; 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-converters/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Converters 2 | 3 | Functions for converting between serialized Lexical and Mobiledoc formats 4 | 5 | ## Install 6 | 7 | `npm install @tryghost/kg-converters --save` 8 | 9 | or 10 | 11 | `yarn add @tryghost/kg-converters` 12 | 13 | ## Usage 14 | 15 | 16 | ## Develop 17 | 18 | This is a monorepo package. 19 | 20 | Follow the instructions for the top-level repo. 21 | 1. `git clone` this repo & `cd` into it as usual 22 | 2. Run `yarn` to install top-level dependencies. 23 | 24 | 25 | 26 | ## Test 27 | 28 | - `yarn lint` run just eslint 29 | - `yarn test` run lint and tests 30 | 31 | 32 | 33 | # Copyright & License 34 | 35 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/rgb-to-hex.js: -------------------------------------------------------------------------------- 1 | export const rgbToHex = (rgb) => { 2 | if (rgb === 'transparent') { 3 | return rgb; 4 | } 5 | 6 | try { 7 | // Extract the red, green, and blue values from the RGB string 8 | const [r, g, b] = rgb.match(/\d+/g); 9 | // Convert each component to hexadecimal 10 | const red = parseInt(r, 10).toString(16).padStart(2, '0'); 11 | const green = parseInt(g, 10).toString(16).padStart(2, '0'); 12 | const blue = parseInt(b, 10).toString(16).padStart(2, '0'); 13 | // Concatenate the hexadecimal values 14 | const hex = `#${red}${green}${blue}`; 15 | return hex; 16 | } catch (e) { 17 | return null; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /packages/kg-html-to-lexical/README.md: -------------------------------------------------------------------------------- 1 | # Html To Lexical 2 | 3 | Convert HTML strings into Lexical editor state objects 4 | 5 | ## Install 6 | 7 | `npm install @tryghost/kg-html-to-lexical --save` 8 | 9 | or 10 | 11 | `yarn add @tryghost/kg-html-to-lexical` 12 | 13 | ## Usage 14 | 15 | 16 | ## Develop 17 | 18 | This is a monorepo package. 19 | 20 | Follow the instructions for the top-level repo. 21 | 1. `git clone` this repo & `cd` into it as usual 22 | 2. Run `yarn` to install top-level dependencies. 23 | 24 | 25 | 26 | ## Test 27 | 28 | - `yarn lint` run just eslint 29 | - `yarn test` run lint and tests 30 | 31 | 32 | 33 | # Copyright & License 34 | 35 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 36 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/get-dynamic-data-nodes.ts: -------------------------------------------------------------------------------- 1 | import {$getRoot} from 'lexical'; 2 | import {$isKoenigCard, KoenigDecoratorNode} from '@tryghost/kg-default-nodes'; 3 | 4 | import type {EditorState} from 'lexical'; 5 | 6 | export default function getDynamicDataNodes(editorState: EditorState): KoenigDecoratorNode[] { 7 | const dynamicNodes: KoenigDecoratorNode[] = []; 8 | 9 | editorState.read(() => { 10 | const root = $getRoot(); 11 | const nodes = root.getChildren(); 12 | 13 | nodes.forEach((node) => { 14 | if ($isKoenigCard(node) && node.hasDynamicData?.()) { 15 | dynamicNodes.push(node); 16 | } 17 | }); 18 | }); 19 | 20 | return dynamicNodes; 21 | } 22 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/assets/icons/kg-dollar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/content/minimal-content.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "children": [ 4 | { 5 | "children": [ 6 | { 7 | "detail": 0, 8 | "format": 0, 9 | "mode": "normal", 10 | "style": "", 11 | "text": "There's a whole lot to discover in this editor. Let us help you settle in.", 12 | "type": "text", 13 | "version": 1 14 | } 15 | ], 16 | "direction": "ltr", 17 | "format": "", 18 | "indent": 0, 19 | "type": "paragraph", 20 | "version": 1 21 | } 22 | ], 23 | "direction": "ltr", 24 | "format": "", 25 | "indent": 0, 26 | "type": "root", 27 | "version": 1 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/kg-card-factory/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Card Factory 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-card-factory --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-card-factory` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 40 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Default Transforms 2 | 3 | Default Lexical Node transforms used across our Koenig packages 4 | 5 | ## Install 6 | 7 | `npm install @tryghost/kg-default-transforms --save` 8 | 9 | or 10 | 11 | `yarn add @tryghost/kg-default-transforms` 12 | 13 | ## Usage 14 | 15 | 16 | ## Develop 17 | 18 | This is a monorepo package. 19 | 20 | Follow the instructions for the top-level repo. 21 | 1. `git clone` this repo & `cd` into it as usual 22 | 2. Run `yarn` to install top-level dependencies. 23 | 24 | 25 | 26 | ## Test 27 | 28 | - `yarn lint` run just eslint 29 | - `yarn test` run lint and tests 30 | 31 | 32 | 33 | # Copyright & License 34 | 35 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Default Atoms 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-default-atoms --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-default-atoms` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 40 | -------------------------------------------------------------------------------- /packages/kg-default-cards/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Default Cards 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-default-cards --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-default-cards` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 40 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/IconButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import {Tooltip} from './Tooltip'; 4 | 5 | export function IconButton({className, onClick, label, dataTestId, Icon}) { 6 | return ( 7 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/kg-clean-basic-html/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Clean Basic Html 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-clean-basic-html --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-clean-basic-html` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 40 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/aside/AsideParser.js: -------------------------------------------------------------------------------- 1 | export class AsideParser { 2 | constructor(NodeClass) { 3 | this.NodeClass = NodeClass; 4 | } 5 | 6 | get DOMConversionMap() { 7 | const self = this; 8 | 9 | return { 10 | blockquote: () => ({ 11 | conversion(domNode) { 12 | const isBigQuote = domNode.classList?.contains('kg-blockquote-alt'); 13 | if (domNode.tagName === 'BLOCKQUOTE' && isBigQuote) { 14 | const node = new self.NodeClass(); 15 | return {node}; 16 | } 17 | 18 | return null; 19 | }, 20 | priority: 0 21 | }) 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/markdown/MarkdownNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {renderMarkdownNode} from './markdown-renderer'; 4 | 5 | export class MarkdownNode extends generateDecoratorNode({ 6 | nodeType: 'markdown', 7 | properties: [ 8 | {name: 'markdown', default: '', urlType: 'markdown', wordCount: true} 9 | ], 10 | defaultRenderFn: renderMarkdownNode 11 | }) { 12 | isEmpty() { 13 | return !this.__markdown; 14 | } 15 | } 16 | 17 | export function $createMarkdownNode(dataset) { 18 | return new MarkdownNode(dataset); 19 | } 20 | 21 | export function $isMarkdownNode(node) { 22 | return node instanceof MarkdownNode; 23 | } 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/FloatingButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FloatingButton = ({isOpen, ...props}) => { 4 | return ( 5 |
6 | 9 |  |  10 | 13 |
14 | ); 15 | }; 16 | 17 | export default FloatingButton; 18 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/dataSrcToFile.js: -------------------------------------------------------------------------------- 1 | export async function dataSrcToFile(src, fileName) { 2 | if (!src.startsWith('data:')) { 3 | return; 4 | } 5 | 6 | const mimeType = src.split(',')[0].split(':')[1].split(';')[0]; 7 | 8 | if (!fileName) { 9 | let uuid; 10 | try { 11 | uuid = window.crypto.randomUUID(); 12 | } catch (e) { 13 | uuid = Math.random().toString(36).substring(2, 15); 14 | } 15 | const extension = mimeType.split('/')[1]; 16 | fileName = `data-src-image-${uuid}.${extension}`; 17 | } 18 | 19 | const blob = await fetch(src).then(it => it.blob()); 20 | const file = new File([blob], fileName, {type: mimeType, lastModified: new Date()}); 21 | 22 | return file; 23 | } 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/LinkInputWithSearch.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {LinkInputWithSearch} from './LinkInputWithSearch'; 4 | 5 | const story = { 6 | title: 'Toolbar/LinkInputWithSearch', 7 | component: LinkInputWithSearch, 8 | parameters: { 9 | status: { 10 | type: 'functional' 11 | } 12 | } 13 | }; 14 | export default story; 15 | 16 | const Template = (args) => { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | }; 23 | 24 | export const Empty = Template.bind({}); 25 | Empty.args = { 26 | href: '' 27 | }; 28 | 29 | export const Populated = Template.bind({}); 30 | Populated.args = { 31 | href: 'https://ghost.org' 32 | }; 33 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | // Exclude Playwright test directory from Vitest runs 6 | exclude: [ 7 | '**/node_modules/**', 8 | '**/dist/**', 9 | '**/test/acceptance/**' // Assuming your Playwright tests reside here 10 | ], 11 | coverage: { 12 | exclude: [ 13 | '**/node_modules/**', 14 | '**/dist/**', 15 | '**/test/**', 16 | // anything that is tsx and not inside src/api 17 | 18 | '**/*.tsx' 19 | ], 20 | include: [ 21 | '**/src/api/**' 22 | ] 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/shouldIgnoreEvent.js: -------------------------------------------------------------------------------- 1 | // util to avoid processing events in Koenig when they originate from an editor 2 | // element inside a card 3 | export const shouldIgnoreEvent = (event) => { 4 | if (!event) { 5 | return false; 6 | } 7 | 8 | const {metaKey, key, target} = event; 9 | const isEscape = key === 'Escape'; 10 | const isMetaEnter = metaKey && key === 'Enter'; 11 | 12 | // we want to allow some keys presses to pass through as we 13 | // always override them to toggle card editing mode 14 | if (isEscape || isMetaEnter) { 15 | return false; 16 | } 17 | 18 | const isFromCardEditor = target.matches('input, textarea') || target.cmView || target.cmIgnore || !!target.closest('.cm-editor'); 19 | 20 | return isFromCardEditor; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/kg-markdown-html-renderer/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Markdown Html Renderer 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-markdown-html-renderer --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-markdown-html-renderer` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 40 | -------------------------------------------------------------------------------- /packages/kg-mobiledoc-html-renderer/README.md: -------------------------------------------------------------------------------- 1 | # Koenig Mobiledoc Html Renderer 2 | 3 | ## Install 4 | 5 | `npm install @tryghost/kg-mobiledoc-html-renderer --save` 6 | 7 | or 8 | 9 | `yarn add @tryghost/kg-mobiledoc-html-renderer` 10 | 11 | 12 | ## Usage 13 | 14 | 15 | ## Develop 16 | 17 | This is a mono repository, managed with [lerna](https://lernajs.io/). 18 | 19 | Follow the instructions for the top-level repo. 20 | 1. `git clone` this repo & `cd` into it as usual 21 | 2. Run `yarn` to install top-level dependencies. 22 | 23 | 24 | ## Run 25 | 26 | - `yarn dev` 27 | 28 | 29 | ## Test 30 | 31 | - `yarn lint` run just eslint 32 | - `yarn test` run lint and tests 33 | 34 | 35 | 36 | 37 | # Copyright & License 38 | 39 | Copyright (c) 2013-2025 Ghost Foundation - Released under the [MIT license](LICENSE). 40 | -------------------------------------------------------------------------------- /packages/kg-default-transforms/src/transforms/merge-list-nodes.ts: -------------------------------------------------------------------------------- 1 | import {$isListNode, ListNode} from '@lexical/list'; 2 | import {LexicalEditor} from 'lexical'; 3 | 4 | export function mergeListNodesTransform(node: ListNode) { 5 | const nextSibling = node.getNextSibling(); 6 | 7 | if ($isListNode(nextSibling) && $isListNode(node) && nextSibling.getListType() === node.getListType()) { 8 | node.append(...nextSibling.getChildren()); 9 | nextSibling.remove(); 10 | } 11 | } 12 | 13 | export function registerMergeListNodesTransform(editor: LexicalEditor) { 14 | if (editor.hasNodes([ListNode])) { 15 | return editor.registerNodeTransform(ListNode, mergeListNodesTransform); 16 | } 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-empty-function 19 | return () => {}; 20 | } 21 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/test/utils/rgb-to-hex.test.js: -------------------------------------------------------------------------------- 1 | // // import {rgbToHex} from '../../lib/utils/rgb-to-hex'; 2 | const {utils} = require('../../'); 3 | const rgbToHex = utils.rgbToHex; 4 | 5 | describe('rgbToHex', function () { 6 | it('should convert RGB to HEX', function () { 7 | should(rgbToHex('rgb(0, 0, 0)')).equal('#000000'); 8 | should(rgbToHex('rgb(255, 255, 255)')).equal('#ffffff'); 9 | should(rgbToHex('rgb(255, 0, 0)')).equal('#ff0000'); 10 | should(rgbToHex('rgb(0, 255, 0)')).equal('#00ff00'); 11 | should(rgbToHex('rgb(0, 0, 255)')).equal('#0000ff'); 12 | should(rgbToHex('rgb(255, 255, 0)')).equal('#ffff00'); 13 | }); 14 | 15 | it('should handle transparent', function () { 16 | should(rgbToHex('transparent')).equal('transparent'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/HighlightedString.jsx: -------------------------------------------------------------------------------- 1 | import escapeRegExp from 'lodash/escapeRegExp'; 2 | 3 | export function HighlightedString({string, highlightString, shouldHighlight = true}) { 4 | if (!highlightString || shouldHighlight === false) { 5 | return string; 6 | } 7 | 8 | const parts = string.split(new RegExp(`(${escapeRegExp(highlightString)})`, 'gi')); 9 | 10 | return ( 11 | <> 12 | {parts.map((part, index) => { 13 | if (part.toLowerCase() === highlightString.toLowerCase()) { 14 | // eslint-disable-next-line react/no-array-index-key 15 | return {part}; 16 | } 17 | 18 | return part; 19 | })} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/EmojiPicker.jsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useRef} from 'react'; 2 | import {Picker} from 'emoji-mart'; 3 | 4 | export default function EmojiPicker({setInstanceRef, ...props}) { 5 | const ref = useRef(null); 6 | const instance = useRef(null); 7 | 8 | function setInstance(newInstance) { 9 | instance.current = newInstance; 10 | setInstanceRef?.(newInstance); 11 | } 12 | 13 | if (instance.current) { 14 | instance.current.update(props); 15 | } 16 | 17 | useEffect(() => { 18 | setInstance(new Picker({...props, ref})); 19 | 20 | return () => { 21 | setInstance(null); 22 | }; 23 | // eslint-disable-next-line react-hooks/exhaustive-deps 24 | }, []); 25 | 26 | return React.createElement('div', {ref}); 27 | } 28 | -------------------------------------------------------------------------------- /packages/kg-default-atoms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tryghost/kg-default-atoms", 3 | "version": "5.1.1", 4 | "repository": "https://github.com/TryGhost/Koenig/tree/master/packages/kg-default-atoms", 5 | "author": "Ghost Foundation", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "dev": "echo \"Implement me!\"", 10 | "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'", 11 | "lint": "eslint . --ext .js --cache", 12 | "posttest": "yarn lint" 13 | }, 14 | "engines": { 15 | "node": "^18.12.1 || ^20.11.1 || ^22.13.1" 16 | }, 17 | "files": [ 18 | "index.js", 19 | "lib" 20 | ], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "devDependencies": { 25 | "c8": "10.1.3", 26 | "simple-dom": "1.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/file-selectors/Tenor/Error.jsx: -------------------------------------------------------------------------------- 1 | import {ERROR_TYPE} from '../../../../utils/services/tenor.js'; 2 | 3 | export function Error({error}) { 4 | if (error === ERROR_TYPE.COMMON) { 5 | return ( 6 |

7 | Uh-oh! Trouble reaching the Tenor API, please check your connection 8 |

9 | ); 10 | } 11 | 12 | if (error === ERROR_TYPE.INVALID_API_KEY) { 13 | return ( 14 |

15 | This version of the Tenor API is no longer supported. Please update your API key by following our 16 | documentation here. 17 |

18 | ); 19 | } 20 | return ( 21 |

{error}

22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/hooks/usePreviousFocus.js: -------------------------------------------------------------------------------- 1 | import {useRef} from 'react'; 2 | 3 | export const usePreviousFocus = (onClick, name) => { 4 | const previousRangeRef = useRef(null); 5 | 6 | const handleMousedown = () => { 7 | const selection = document.getSelection(); 8 | previousRangeRef.current = (selection.rangeCount === 0 ? null : selection.getRangeAt(0)); 9 | }; 10 | 11 | const handleClick = (e) => { 12 | e.preventDefault(); 13 | onClick(name); 14 | 15 | if (previousRangeRef.current) { 16 | const selection = document.getSelection(); 17 | selection.removeAllRanges(); 18 | selection.addRange(previousRangeRef.current); 19 | previousRangeRef.current = null; 20 | } 21 | }; 22 | 23 | return {handleMousedown, handleClick}; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/getDOMRangeRect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | export function getDOMRangeRect( 9 | nativeSelection, 10 | rootElement, 11 | ) { 12 | const domRange = nativeSelection.getRangeAt(0); 13 | 14 | let rect; 15 | 16 | if (nativeSelection.anchorNode === rootElement) { 17 | let inner = rootElement; 18 | while (inner.firstElementChild != null) { // eslint-disable-line eqeqeq 19 | inner = inner.firstElementChild; 20 | } 21 | rect = inner.getBoundingClientRect(); 22 | } else { 23 | rect = domRange.getBoundingClientRect(); 24 | } 25 | 26 | return rect; 27 | } 28 | -------------------------------------------------------------------------------- /packages/kg-default-cards/test/cards/paywall.test.js: -------------------------------------------------------------------------------- 1 | // Switch these lines once there are useful utils 2 | // const testUtils = require('./utils'); 3 | require('../utils'); 4 | 5 | const card = require('../../lib/cards/paywall'); 6 | const SimpleDom = require('simple-dom'); 7 | const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap); 8 | 9 | describe('paywall card', function () { 10 | it('has correct properties', function () { 11 | card.name.should.eql('paywall'); 12 | card.type.should.eql('dom'); 13 | }); 14 | 15 | it('generates a members-only comment', function () { 16 | let opts = { 17 | env: { 18 | dom: new SimpleDom.Document() 19 | } 20 | }; 21 | 22 | serializer.serialize(card.render(opts)).should.match(''); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/kg-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tryghost/kg-utils", 3 | "version": "1.0.34", 4 | "repository": "https://github.com/TryGhost/Koenig/tree/main/packages/kg-utils", 5 | "author": "Ghost Foundation", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "dev": "echo \"Implement me!\"", 10 | "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'", 11 | "lint": "eslint . --ext .js --cache", 12 | "posttest": "yarn lint" 13 | }, 14 | "files": [ 15 | "index.js", 16 | "lib" 17 | ], 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "devDependencies": { 22 | "c8": "10.1.3", 23 | "mocha": "11.7.5", 24 | "should": "13.2.3", 25 | "sinon": "21.0.0" 26 | }, 27 | "dependencies": { 28 | "semver": "^7.7.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/sanitize-html.js: -------------------------------------------------------------------------------- 1 | import DOMPurify from 'dompurify'; 2 | 3 | export function sanitizeHtml(html = '', options = {}) { 4 | options = { 5 | ...{replaceJS: true}, 6 | ...options 7 | }; 8 | 9 | // replace script and iFrame 10 | if (options.replaceJS) { 11 | html = html.replace(/)<[^<]*)*<\/script>/gi, 12 | '
Embedded JavaScript
'); 13 | html = html.replace(/)<[^<]*)*<\/iframe>/gi, 14 | '
Embedded iFrame
'); 15 | } 16 | 17 | // sanitize html 18 | return DOMPurify.sanitize(html, { 19 | ALLOWED_URI_REGEXP: /^https?:|^\/|blob:/, 20 | ADD_ATTR: ['id'], 21 | FORBID_TAGS: ['style'] 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/file-selectors/Tenor/Loader.jsx: -------------------------------------------------------------------------------- 1 | export function Loader({isLazyLoading}) { 2 | if (isLazyLoading) { 3 | return ( 4 |
5 |
6 |
7 | ); 8 | } 9 | return ( 10 |
11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>tryghost/renovate-config" 4 | ], 5 | "ignoreDeps": [ 6 | "codecov/codecov-action" 7 | ], 8 | "packageRules": [ 9 | { 10 | "depTypeList": [ 11 | "devDependencies" 12 | ], 13 | "automerge": true, 14 | "automergeType": "branch" 15 | }, 16 | { 17 | "groupName": "rollup + plugins", 18 | "packagePatterns": [ 19 | "^rollup" 20 | ] 21 | }, 22 | { 23 | "groupName": "lexical", 24 | "packagePatterns": [ 25 | "^lexical", 26 | "^@lexical" 27 | ] 28 | }, 29 | { 30 | "groupName": "vitest", 31 | "packagePatterns": [ 32 | "^vitest", 33 | "^@vitest" 34 | ] 35 | }, 36 | { 37 | "matchPackageNames": ["@tryghost/mobiledoc-kit"], 38 | "enabled": false 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/transformers/element/aside.ts: -------------------------------------------------------------------------------- 1 | import {$isAsideNode, RendererOptions} from '@tryghost/kg-default-nodes'; 2 | import type {ElementNode} from 'lexical'; 3 | import type {ExportChildren} from '..'; 4 | 5 | module.exports = { 6 | export(node: ElementNode, options: RendererOptions, exportChildren: ExportChildren) { 7 | if (!$isAsideNode(node)) { 8 | return null; 9 | } 10 | 11 | if (options.target === 'email') { 12 | let children = exportChildren(node); 13 | 14 | if (!children.startsWith('

')) { 15 | children = `

${children}

`; 16 | } 17 | 18 | return `
${children}
`; 19 | } 20 | 21 | return `
${exportChildren(node)}
`; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /packages/kg-lexical-html-renderer/lib/transformers/element/blockquote.ts: -------------------------------------------------------------------------------- 1 | import {$isQuoteNode} from '@lexical/rich-text'; 2 | import type {RendererOptions} from '@tryghost/kg-default-nodes'; 3 | import type {ElementNode} from 'lexical'; 4 | import type {ExportChildren} from '..'; 5 | 6 | module.exports = { 7 | export(node: ElementNode, options: RendererOptions, exportChildren: ExportChildren) { 8 | if (!$isQuoteNode(node)) { 9 | return null; 10 | } 11 | 12 | if (options.target === 'email') { 13 | let children = exportChildren(node); 14 | 15 | if (!children.startsWith('

')) { 16 | children = `

${children}

`; 17 | } 18 | 19 | return `
${children}
`; 20 | } 21 | 22 | return `
${exportChildren(node)}
`; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import SerializedStateTextarea from './SerializedStateTextarea'; 2 | import TreeView from './TreeView'; 3 | 4 | const Sidebar = ({isOpen, view, saveContent}) => { 5 | return ( 6 |
7 | {view === 'json' && } 8 | {view === 'tree' && } 9 | 10 | {view === 'json' && ( 11 |
12 | 13 |
14 | )} 15 |
16 | ); 17 | }; 18 | 19 | export default Sidebar; 20 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/test/test-utils/assertions.js: -------------------------------------------------------------------------------- 1 | const Prettier = require('@prettier/sync'); 2 | const chai = require('chai'); 3 | const expect = chai.expect; 4 | const minify = require('html-minifier').minify; 5 | 6 | should.Assertion.add('prettifyTo', function (str) { 7 | const minifiedExpected = minify(str, {collapseWhitespace: true, collapseInlineTagWhitespace: true}); 8 | const expectedStr = Prettier.format(minifiedExpected, {parser: 'html'}); 9 | 10 | this.params = { 11 | operator: 'to prettify to `' + str + '`', 12 | expected: expectedStr, 13 | showDiff: true 14 | }; 15 | 16 | this.obj.should.be.a.String; 17 | const minified = minify(this.obj, {collapseWhitespace: true, collapseInlineTagWhitespace: true}); 18 | const result = Prettier.format(minified, {parser: 'html'}); 19 | expect(result).to.equal(expectedStr); 20 | }, false); 21 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/html/HtmlNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {renderHtmlNode} from './html-renderer'; 4 | import {parseHtmlNode} from './html-parser'; 5 | 6 | export class HtmlNode extends generateDecoratorNode({ 7 | nodeType: 'html', 8 | hasVisibility: true, 9 | properties: [ 10 | {name: 'html', default: '', urlType: 'html', wordCount: true} 11 | ], 12 | defaultRenderFn: renderHtmlNode 13 | }) { 14 | static importDOM() { 15 | return parseHtmlNode(this); 16 | } 17 | 18 | isEmpty() { 19 | return !this.__html; 20 | } 21 | } 22 | 23 | export function $createHtmlNode(dataset) { 24 | return new HtmlNode(dataset); 25 | } 26 | 27 | export function $isHtmlNode(node) { 28 | return node instanceof HtmlNode; 29 | } 30 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/toggle/ToggleNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {parseToggleNode} from './toggle-parser'; 4 | import {renderToggleNode} from './toggle-renderer'; 5 | 6 | export class ToggleNode extends generateDecoratorNode({ 7 | nodeType: 'toggle', 8 | properties: [ 9 | {name: 'heading', default: '', urlType: 'html', wordCount: true}, 10 | {name: 'content', default: '', urlType: 'html', wordCount: true} 11 | ], 12 | defaultRenderFn: renderToggleNode 13 | }) { 14 | static importDOM() { 15 | return parseToggleNode(this); 16 | } 17 | } 18 | 19 | export const $createToggleNode = (dataset) => { 20 | return new ToggleNode(dataset); 21 | }; 22 | 23 | export function $isToggleNode(node) { 24 | return node instanceof ToggleNode; 25 | } 26 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/size-byte-converter.js: -------------------------------------------------------------------------------- 1 | export function sizeToBytes(size) { 2 | if (!size) { 3 | return 0; 4 | } 5 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 6 | const sizeParts = size.split(' '); 7 | const sizeNumber = parseFloat(sizeParts[0]); 8 | const sizeUnit = sizeParts[1]; 9 | const sizeUnitIndex = sizes.indexOf(sizeUnit); 10 | if (sizeUnitIndex === -1) { 11 | return 0; 12 | } 13 | return Math.round(sizeNumber * Math.pow(1024, sizeUnitIndex)); 14 | } 15 | 16 | export function bytesToSize(bytes) { 17 | if (!bytes) { 18 | return '0 Byte'; 19 | } 20 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 21 | if (bytes === 0) { 22 | return '0 Byte'; 23 | } 24 | const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); 25 | return Math.round((bytes / Math.pow(1024, i))) + ' ' + sizes[i]; 26 | } -------------------------------------------------------------------------------- /packages/koenig-lexical/src/assets/icons/kg-card-type-spotify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/button/ButtonNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {parseButtonNode} from './button-parser'; 4 | import {renderButtonNode} from './button-renderer'; 5 | 6 | export class ButtonNode extends generateDecoratorNode({ 7 | nodeType: 'button', 8 | properties: [ 9 | {name: 'buttonText', default: ''}, 10 | {name: 'alignment', default: 'center'}, 11 | {name: 'buttonUrl', default: '', urlType: 'url'} 12 | ], 13 | defaultRenderFn: renderButtonNode 14 | }) { 15 | static importDOM() { 16 | return parseButtonNode(this); 17 | } 18 | } 19 | 20 | export const $createButtonNode = (dataset) => { 21 | return new ButtonNode(dataset); 22 | }; 23 | 24 | export function $isButtonNode(node) { 25 | return node instanceof ButtonNode; 26 | } 27 | -------------------------------------------------------------------------------- /packages/koenig-lexical/demo/components/TreeView.jsx: -------------------------------------------------------------------------------- 1 | import {TreeView} from '@lexical/react/LexicalTreeView'; 2 | import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; 3 | 4 | const TreeViewPlugin = () => { 5 | const [editor] = useLexicalComposerContext(); 6 | 7 | return ( 8 | 16 | ); 17 | }; 18 | 19 | export default TreeViewPlugin; 20 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/KoenigEditor.jsx: -------------------------------------------------------------------------------- 1 | import '../styles/index.css'; 2 | import KoenigComposableEditor from './KoenigComposableEditor'; 3 | import React from 'react'; 4 | import {AllDefaultPlugins} from '../plugins/AllDefaultPlugins'; 5 | import {SharedHistoryContext} from '../context/SharedHistoryContext'; 6 | import {SharedOnChangeContext} from '../context/SharedOnChangeContext'; 7 | 8 | const KoenigEditor = ({ 9 | onChange, 10 | children, 11 | ...props 12 | }) => { 13 | return ( 14 | 15 | 16 | 17 | 18 | {children} 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default KoenigEditor; 26 | -------------------------------------------------------------------------------- /packages/kg-card-factory/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tryghost/kg-card-factory", 3 | "version": "5.1.4", 4 | "repository": "https://github.com/TryGhost/Koenig/tree/master/packages/kg-card-factory", 5 | "author": "Ghost Foundation", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "dev": "echo \"Implement me!\"", 10 | "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'", 11 | "lint": "eslint . --ext .js --cache", 12 | "posttest": "yarn lint" 13 | }, 14 | "engines": { 15 | "node": "^18.12.1 || ^20.11.1 || ^22.13.1" 16 | }, 17 | "files": [ 18 | "index.js", 19 | "lib" 20 | ], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "devDependencies": { 25 | "c8": "10.1.3", 26 | "mocha": "11.7.5", 27 | "should": "13.2.3", 28 | "simple-dom": "1.4.0", 29 | "sinon": "21.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ImageUploadSwatch.jsx: -------------------------------------------------------------------------------- 1 | import ImgBgIcon from '../../assets/icons/kg-img-bg.svg?react'; 2 | import clsx from 'clsx'; 3 | import {Tooltip} from './Tooltip'; 4 | 5 | export const ImageUploadSwatch = ({ 6 | showBackgroundImage, 7 | onClickHandler, 8 | dataTestId 9 | }) => { 10 | return ( 11 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/kg-converters/test/exports.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert/strict'); 2 | 3 | describe('Exports', function () { 4 | it('includes both converter functions', function () { 5 | const converters = require('../'); 6 | 7 | assert.ok(converters); 8 | assert.ok(converters.lexicalToMobiledoc); 9 | assert.equal(typeof converters.lexicalToMobiledoc, 'function'); 10 | assert.ok(converters.mobiledocToLexical); 11 | assert.equal(typeof converters.mobiledocToLexical, 'function'); 12 | }); 13 | 14 | it('lexicalToMobiledoc runs without error', function () { 15 | const converters = require('../'); 16 | assert.ok(converters.lexicalToMobiledoc('{}')); 17 | }); 18 | 19 | it('mobiledocToLexical runs without error', function () { 20 | const converters = require('../'); 21 | assert.ok(converters.mobiledocToLexical('{}')); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/horizontalrule/HorizontalRuleNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {renderHorizontalRuleNode} from './horizontalrule-renderer'; 4 | import {parseHorizontalRuleNode} from './horizontalrule-parser'; 5 | 6 | export class HorizontalRuleNode extends generateDecoratorNode({ 7 | nodeType: 'horizontalrule', 8 | defaultRenderFn: renderHorizontalRuleNode 9 | }) { 10 | static importDOM() { 11 | return parseHorizontalRuleNode(this); 12 | } 13 | 14 | getTextContent() { 15 | return '---\n\n'; 16 | } 17 | 18 | hasEditMode() { 19 | return false; 20 | } 21 | } 22 | 23 | export function $createHorizontalRuleNode() { 24 | return new HorizontalRuleNode(); 25 | } 26 | 27 | export function $isHorizontalRuleNode(node) { 28 | return node instanceof HorizontalRuleNode; 29 | } 30 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/ColorPicker.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ColorPicker} from './ColorPicker'; 3 | 4 | const story = { 5 | title: 'Generic/Color picker (New)', 6 | component: ColorPicker, 7 | parameters: { 8 | status: { 9 | type: 'uiReady' 10 | } 11 | }, 12 | argTypes: { 13 | selectedName: {control: 'select', options: ['grey', 'blue', 'green', 'yellow', 'red', 'pink', 'purple']} 14 | } 15 | }; 16 | export default story; 17 | 18 | const Template = (args) => { 19 | return ( 20 |
21 | 22 |
23 | ); 24 | }; 25 | 26 | export const Default = Template.bind({}); 27 | Default.args = { 28 | swatches: [ 29 | {title: 'Brand color', accent: true}, 30 | {title: 'Black', hex: '#000000'}, 31 | {title: 'Transparent', transparent: true} 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/components/ui/Dropdown.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Dropdown} from './Dropdown'; 4 | 5 | const story = { 6 | title: 'Generic/Dropdown', 7 | component: Dropdown, 8 | argTypes: { 9 | value: {control: 'radio', options: ['Free members', 'Paid members']} 10 | }, 11 | parameters: { 12 | status: { 13 | type: 'uiReady' 14 | } 15 | } 16 | }; 17 | export default story; 18 | 19 | const Template = args => ( 20 |
21 | 22 |
23 | ); 24 | 25 | export const Default = Template.bind({}); 26 | Default.args = { 27 | label: 'Visibility', 28 | description: 'Visible for this audience when delivered by email. This card is not published on your site.', 29 | value: 'Free members', 30 | menu: [{label: 'Free members', name: 'Free members'}, {label: 'Paid members', name: 'Paid members'}] 31 | }; 32 | -------------------------------------------------------------------------------- /packages/kg-unsplash-selector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "module": "ESNext", 11 | "moduleResolution": "Node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "react-jsx", 15 | "skipLibCheck": true, 16 | "esModuleInterop": true, 17 | "declarationMap": true, 18 | "declaration": true, 19 | "emitDeclarationOnly": true, 20 | "declarationDir": "./types", 21 | "baseUrl": ".", 22 | "paths": { 23 | "kg-unsplash-selector": ["src/index.ts"], 24 | }, 25 | "typeRoots": ["node_modules/@types", "types/index.d.ts"], 26 | }, 27 | "include": ["src"], 28 | "references": [{ "path": "./tsconfig.node.json" }], 29 | } 30 | -------------------------------------------------------------------------------- /packages/koenig-lexical/src/utils/$insertAndSelectNode.js: -------------------------------------------------------------------------------- 1 | import { 2 | $createNodeSelection, 3 | $createParagraphNode, 4 | $isParagraphNode, 5 | $setSelection 6 | } from 'lexical'; 7 | 8 | export const $insertAndSelectNode = ({selectedNode, newNode}) => { 9 | const selectedIsParagraph = $isParagraphNode(selectedNode); 10 | const selectedIsEmpty = selectedNode.getTextContent() === ''; 11 | 12 | selectedNode 13 | .insertAfter(newNode); 14 | 15 | if (selectedIsParagraph && selectedIsEmpty) { 16 | selectedNode.remove(); 17 | } 18 | 19 | const nodeSelection = $createNodeSelection(); 20 | nodeSelection.add(newNode.getKey()); 21 | $setSelection(nodeSelection); 22 | 23 | // always follow the inserted card with a blank paragraph when inserting at end of document 24 | if (!newNode.getNextSibling()) { 25 | const paragraph = $createParagraphNode(); 26 | newNode.insertAfter(paragraph); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/nodes/audio/AudioNode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ghost/filenames/match-exported-class */ 2 | import {generateDecoratorNode} from '../../generate-decorator-node'; 3 | import {parseAudioNode} from './audio-parser'; 4 | import {renderAudioNode} from './audio-renderer'; 5 | 6 | export class AudioNode extends generateDecoratorNode({ 7 | nodeType: 'audio', 8 | properties: [ 9 | {name: 'duration', default: 0}, 10 | {name: 'mimeType', default: ''}, 11 | {name: 'src', default: '', urlType: 'url'}, 12 | {name: 'title', default: ''}, 13 | {name: 'thumbnailSrc', default: ''} 14 | ], 15 | defaultRenderFn: renderAudioNode 16 | }) { 17 | static importDOM() { 18 | return parseAudioNode(this); 19 | } 20 | } 21 | 22 | export const $createAudioNode = (dataset) => { 23 | return new AudioNode(dataset); 24 | }; 25 | 26 | export function $isAudioNode(node) { 27 | return node instanceof AudioNode; 28 | } 29 | -------------------------------------------------------------------------------- /packages/kg-default-nodes/lib/utils/add-create-document-option.js: -------------------------------------------------------------------------------- 1 | // If we're in a browser environment, we can use the global document object, 2 | // but if we're in a non-browser environment, we need to be passed a `createDocument` function 3 | export function addCreateDocumentOption(options) { 4 | if (!options.createDocument && options.dom) { 5 | options.createDocument = function () { 6 | return options.dom.window.document; 7 | }; 8 | } 9 | 10 | if (!options.createDocument) { 11 | /* c8 ignore start */ 12 | let document = typeof window !== 'undefined' && window.document; 13 | 14 | if (!document) { 15 | throw new Error('Must be passed a `createDocument` function as an option when used in a non-browser environment'); // eslint-disable-line 16 | } 17 | 18 | options.createDocument = function () { 19 | return document; 20 | }; 21 | /* c8 ignore end */ 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/html-to-mobiledoc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tryghost/html-to-mobiledoc", 3 | "version": "3.2.10", 4 | "repository": "https://github.com/TryGhost/Koenig/tree/master/packages/html-to-mobiledoc", 5 | "author": "Ghost Foundation", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "files": [ 9 | "index.js", 10 | "lib" 11 | ], 12 | "scripts": { 13 | "dev": "echo \"Implement me!\"", 14 | "test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'", 15 | "lint": "eslint . --ext .js --cache", 16 | "posttest": "yarn lint" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "devDependencies": { 22 | "c8": "10.1.3", 23 | "mocha": "11.7.5", 24 | "should": "13.2.3", 25 | "sinon": "21.0.0" 26 | }, 27 | "dependencies": { 28 | "@tryghost/kg-parser-plugins": "4.2.9", 29 | "@tryghost/mobiledoc-kit": "^0.12.4-ghost.1", 30 | "jsdom": "^24.1.1" 31 | } 32 | } 33 | --------------------------------------------------------------------------------