TipTap

CodeMirrorではMarkdown記法のライブ変換に限界があったため、TipTapを試してみる。

参考

まずはpnpmとViteで準備
> pnpm create vite my-tiptap-editor
> cd my-tiptap-editor
> pnpm install
> pnpm run dev

最小限のEditorコンポーネントを作成(StarterKit使用)
import React from 'react';
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
const Editor: React.FC = () => {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Hello World!</p>',
});
return (
<div>
<EditorContent editor={editor} />
</div>
);
};
export default Editor;
Appコンポーネントを変更
import './App.css'
import Editor from './components/Editor'
function App() {
return (
<>
<Editor />
</>
)
}
export default App

スタイルがついていたため、index.css
とApp.css
を全てコメントアウト
ブラウザデフォルトのスタイルを取り除くためにApp.css
に以下のdestyle.cssを追記
/*! destyle.css v4.0.1 | MIT License | https://github.com/nicolas-cusan/destyle.css */
/* Reset box-model and set borders */
/* ============================================ */
*,
::before,
::after {
box-sizing: border-box;
border-style: solid;
border-width: 0;
min-width: 0;
}
/* Document */
/* ============================================ */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
* 3. Remove gray overlay on links for iOS.
*/
html {
line-height: 1.15;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-webkit-tap-highlight-color: transparent;
/* 3*/
}
/* Sections */
/* ============================================ */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/* Vertical rhythm */
/* ============================================ */
p,
table,
blockquote,
address,
pre,
iframe,
form,
figure,
dl {
margin: 0;
}
/* Headings */
/* ============================================ */
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
margin: 0;
}
/* Lists (enumeration) */
/* ============================================ */
ul,
ol {
margin: 0;
padding: 0;
list-style: none;
}
/* Lists (definition) */
/* ============================================ */
dt {
font-weight: bold;
}
dd {
margin-left: 0;
}
/* Grouping content */
/* ============================================ */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box;
/* 1 */
height: 0;
/* 1 */
overflow: visible;
/* 2 */
border-top-width: 1px;
margin: 0;
clear: both;
color: inherit;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace;
/* 1 */
font-size: inherit;
/* 2 */
}
address {
font-style: inherit;
}
/* Text-level semantics */
/* ============================================ */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
text-decoration: none;
color: inherit;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
text-decoration: underline dotted;
/* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace;
/* 1 */
font-size: inherit;
/* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Replaced content */
/* ============================================ */
/**
* Prevent vertical alignment issues.
*/
svg,
img,
embed,
object,
iframe {
vertical-align: bottom;
}
/* Forms */
/* ============================================ */
/**
* Reset form fields to make them styleable.
* 1. Make form elements stylable across systems iOS especially.
* 2. Inherit text-transform from parent.
*/
button,
input,
optgroup,
select,
textarea {
-webkit-appearance: none;
/* 1 */
appearance: none;
vertical-align: middle;
color: inherit;
font: inherit;
background: transparent;
padding: 0;
margin: 0;
border-radius: 0;
text-align: inherit;
text-transform: inherit;
/* 2 */
}
/**
* Correct cursors for clickable elements.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
cursor: pointer;
}
button:disabled,
[type="button"]:disabled,
[type="reset"]:disabled,
[type="submit"]:disabled {
cursor: default;
}
/**
* Improve outlines for Firefox and unify style with input elements & buttons.
*/
:-moz-focusring {
outline: auto;
}
select:disabled {
opacity: inherit;
}
/**
* Remove padding
*/
option {
padding: 0;
}
/**
* Reset to invisible
*/
fieldset {
margin: 0;
padding: 0;
min-width: 0;
}
legend {
padding: 0;
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* Correct the outline style in Safari.
*/
[type="search"] {
outline-offset: -2px;
/* 1 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Fix font inheritance.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/**
* Fix appearance for Firefox
*/
[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
}
/**
* Clickable labels
*/
label[for] {
cursor: pointer;
}
/* Interactive */
/* ============================================ */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/*
* Remove outline for editable content.
*/
[contenteditable]:focus {
outline: auto;
}
/* Tables */
/* ============================================ */
/**
1. Correct table border color inheritance in all Chrome and Safari.
*/
table {
border-color: inherit;
/* 1 */
border-collapse: collapse;
}
caption {
text-align: left;
}
td,
th {
vertical-align: top;
padding: 0;
}
th {
text-align: left;
font-weight: bold;
}

ここまででこんな感じ

わかったこと
- 入力欄は各行ごとに<p>タグで囲われている
- StarterKitを適用していると、特定の文字が入力されたタイミングで、<p>タグから任意のタグに変わる
Starterkitでできること
参考:https://zenn.dev/yuu104/articles/8bbbbaabe52e1e
Blockquote : <blockquote>タグを使用できる
BulletList : <ul>タグを使用できる
CodeBlock : <pre>と<code>タグによるコードブロックを使用できる
Document : <body>タグのようなもの
HardBreak :
タグで改行できる
Heading : <h1>〜<h6>タグを使用できる
HorizonalRule : <hr>タグを使用できる
ListItem : <li>タグを使用できる
OrderedList : <ol>タグを使用できる
Paragraph : 段落を扱うために必要
Text : 何らかのテキストを扱う場合に必要
Bold : テキストを太字にする
Code : <code>タグを使用できる
Italic : テキストを斜体で表示する
Strike : テキストに打ち消し線を引く
History : エディタ編集の履歴をサポートする
Dropcursor
Gapcursor

Markdown記法がHTMLにライブ変換されることが分かったので、とりあえずCSSを当ててみる。
> pnpm add zenn-content-css
import React from 'react';
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import 'zenn-content-css';
const Editor: React.FC = () => {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Hello World!</p>',
});
return (
<div>
<div className="znc">
<EditorContent editor={editor} />
</div>
</div>
);
};
export default Editor;

import 'zenn-content-css';
で型が無いと怒られたので色々調整
(Nextのときは怒られなかった気がする)
src/types/zenn-content-css.d.ts
を作成して、以下を記載
declare module 'zenn-content-css';

合わせてここら辺も変更
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"include": [
"src",
"src/types" // 追加: 型定義ファイルを含める
]
}
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
// "noEmit": true, // 出力生成を無効にしている場合は削除またはコメントアウト
"emitDeclarationOnly": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"composite": true,
"declaration": true
},
"include": ["src"]
}
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
// "noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"composite": true
},
"include": ["vite.config.ts"]
}

ライブ変換で見た目がいい感じになった

問題点としては```とした時点でcodeブロックに変換されるから、codeブロックの中の言語やファイル名を書けないことかな。
TipTapの公式サイトでcodeブロックにシンタックスハイライトが当たっているから解決策はあるんだろう。
追記
言語の指定は以下で解決できそう
<code>
に変わった後の言語の変更もできる

公式ドキュメントが分かりやすくていい
公式ドキュメントの中でもトップクラスにわかりやすくて感動 🔥