ドキュメント作成の準備

This commit is contained in:
Akira
2026-03-03 12:56:26 +09:00
parent 07258bb46d
commit 1be261c95f
6 changed files with 965 additions and 7 deletions

View File

@@ -121,17 +121,43 @@ Amazon の `/api/behaviors/preview` エンドポイントが、
---
## 次に試すべきこと
## 試行ログ続き2026-03-03 午後)
- [ ] `auth4.js` Cookie 新規取得してテスト(セッションリセット
- [ ] `/api/behaviors/preview` 以外のエンドポイントを探す(例: `/api/ap/d-notification`など)
- [ ] `Alexa.TextCommand` ノードタイプ(「テキストで命令を送る」別ルート)
- [ ] ローカルブリッジ方式ローカルPCをプロキシにしてAmazonにリクエストを転送する
- [ ] alexa-cookie / alexa-remote2 のソースコードから別APIを調査する
#### Cookie 新規取得auth4.js 再実行
→ 変化なし。Cookie は原因ではなかった。
#### ❌ `AlexaAnnouncement` ノードタイプ
→ 「えんえせんと」("AlexaAnnouncement" を日本語で読んだ)。コンテンツではなくノード名が読まれた。別用途のノード。
#### ✅ **解決!** `sequenceJson` の non-ASCII を `\uXXXX` エスケープに変換
```javascript
var rawSequenceJson = JSON.stringify(sequenceObj).replace(
/[\u0080-\uffff]/g,
function(c) { return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); }
);
```
→ 「これは日本語のテストです」が完璧に発話された!
---
## ✅ 解決済み2026-03-03
**根本原因**: `sequenceJson` 内の日本語文字を raw UTF-8 のまま Amazon に送ると、Amazon 側のパーサーがそれをフィルタリングして無視する。
**解決策**: `JSON.stringify(sequenceObj)` 後に non-ASCII 文字(`\u0080` 以上)を `\uXXXX` 形式のJSONエスケープシーケンスに変換してから `sequenceJson` として送る。
**修正箇所**: `alexa-api/server.js``alexa-api/test_tts.js`
**確定したパラメータ**:
- `type: 'Alexa.Speak'`
- `locale: 'ja-JP'`
- `textToSpeak: <日本語テキスト>`
- `sequenceJson` は non-ASCII を `\uXXXX` エスケープして送る
---
## 参考
- 実装記録: `docs/alexa-api/10_Alexa TTS API 実装記録 (2026-03-02).md`
- 上記ファイルに記録されていた未解決事項がこのファイルに続く

View File

@@ -0,0 +1,36 @@
{
"formatVersion": 2,
"appVersion": "0.63.7",
"files": [
{
"noteId": "IRnO9uub2Bwg",
"notePath": [
"IRnO9uub2Bwg"
],
"isClone": false,
"title": "Alexa TTS API 実装記録 (2026-03-02)",
"notePosition": 50,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"format": "html",
"dataFileName": "Alexa TTS API 実装記録 (2026-03-02.html",
"noImport": false,
"attributes": [],
"attachments": []
},
{
"noImport": true,
"dataFileName": "navigation.html"
},
{
"noImport": true,
"dataFileName": "index.html"
},
{
"noImport": true,
"dataFileName": "style.css"
}
]
}

View File

@@ -0,0 +1,318 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<base target="_parent">
<title data-trilium-title>Alexa TTS API 実装記録 (2026-03-02)</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Alexa TTS API 実装記録 (2026-03-02)</h1>
<div class="ck-content">
<p>Alexa TTS API マスタードキュメント</p>
<p><strong>最終更新</strong>: 2026-03-03 <strong>状態</strong>: サーバーからの日本語TTS未解決調査中
<br>------------
<br>2026/03/03 10:24 akira記録
<br>akiraが下記の変更をしましたので、内容を読んでください。
<br>
<br>1) 構成とサーバーへのファイル受け渡し方法を変更しました
<br>/home/claude/windmill_workflow
<br>に、https://gitea.keinafarm.net/akira/windmill_workflow.gitをcloneしました
<br>これにより、
<br>C:\Users\akira\Develop\windmill_workflow
<br>とのやり取りはgiteaを使って出来るようになります。
<br>
<br>2) docker compose up -dで、「 Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP
を参照したまま 502/504 エラーになる」のは、原因があるはずです。(他のコンテナで、問題になっていないので)
<br>調査して、Traefik 再起動が不必要になるようにしたいです
<br>
<br>
<br>
<br>------------</p>
<hr>
<h2>概要</h2>
<p>Windmill から家中の Echo デバイスに任意のテキストを読み上げさせる API サーバー。Node.js + Express で実装し、Docker
コンテナとして Windmill サーバー上で稼働する。</p>
<p><strong>⚠ 現在の問題</strong>: ローカル PC からは日本語TTS動作確認済み。しかしサーバーkeinafarm.netのコンテナからリクエストすると日本語文字が
Amazon 側で除去されて発話されない。原因は Amazon の IP ベースフィルタリング海外IPからは日本語 textToSpeak
を無視する模様)。調査継続中。</p>
<h2>ファイル構成</h2>
<figure class="table">
<table>
<thead>
<tr>
<th>ファイル</th>
<th>場所</th>
<th>役割</th>
<th>備考</th>
</tr>
</thead>
<tbody>
<tr>
<td>server.js</td>
<td>alexa-api/(リポジトリ)</td>
<td>Express API サーバー本体</td>
<td>本番コード。変更したらビルド・再デプロイが必要</td>
</tr>
<tr>
<td>Dockerfile</td>
<td>alexa-api/(リポジトリ)</td>
<td>Docker イメージ定義</td>
<td>node:20-alpine ベース。server.js と package*.json をコピー</td>
</tr>
<tr>
<td>docker-compose.yml</td>
<td>alexa-api/(リポジトリ)</td>
<td>コンテナ起動設定</td>
<td>windmill_windmill-internal ネットワーク接続。外部ポート非公開</td>
</tr>
<tr>
<td>package.json / package-lock.json</td>
<td>alexa-api/(リポジトリ)</td>
<td>npm 依存関係</td>
<td>本番: express のみ。devDeps に alexa-remote2不使用</td>
</tr>
<tr>
<td>.env.example</td>
<td>alexa-api/(リポジトリ)</td>
<td>環境変数テンプレート</td>
<td>ALEXA_COOKIE=xxx の形式</td>
</tr>
<tr>
<td>.env</td>
<td>alexa-api/(リポジトリ、.gitignore 対象)</td>
<td>実際の Cookie 保管</td>
<td>Git にコミットしない。ローカル作業後に scp でサーバーへ転送</td>
</tr>
<tr>
<td>auth4.js</td>
<td>alexa-api/(リポジトリ)</td>
<td>Amazon 認証・Cookie 取得スクリプト</td>
<td>ローカルのみで実行Windows PC</td>
</tr>
<tr>
<td>auth.js / auth2.js / auth3.js</td>
<td>alexa-api/(リポジトリ)</td>
<td>auth4.js の旧バージョン</td>
<td>参考用。実際は auth4.js を使う</td>
</tr>
<tr>
<td>test_tts.js</td>
<td>alexa-api/(リポジトリ)</td>
<td>ローカルテスト用スクリプト</td>
<td>直接 alexa.amazon.co.jp を叩いて動作確認</td>
</tr>
</tbody>
</table>
</figure>
<p><strong>サーバー上のファイル場所</strong>: <code>/home/claude/alexa-api/</code>git
リポジトリとは別にコピーして管理)</p>
<h2>サーバーへのデプロイ手順</h2>
<p>server.js や Dockerfile、package.json を変更した場合は以下の手順でサーバーに反映する。</p>
<h3>Step 1: ローカルでファイルを編集</h3>
<p>リポジトリc:\Users\akira\Develop\windmill_workflow\alexa-api\)でファイルを編集する。</p>
<h3>Step 2: scp でサーバーに転送</h3>
<p>変更したファイルをサーバーに scp で転送する:</p>
<p># server.js を変更した場合 scp alexa-api/server.js keinafarm-claude:/home/claude/alexa-api/server.js
# Dockerfile や package.json を変更した場合 scp alexa-api/Dockerfile keinafarm-claude:/home/claude/alexa-api/Dockerfile
scp alexa-api/package.json keinafarm-claude:/home/claude/alexa-api/package.json
scp alexa-api/package-lock.json keinafarm-claude:/home/claude/alexa-api/package-lock.json
# .env を更新した場合Cookie 更新時など) scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env</p>
<h3>Step 3: サーバーでビルドして再起動</h3>
<p><strong>⚠ 重要</strong>: <code>docker compose restart</code> はイメージをリビルドしない。server.js
等を変更した場合は必ず <code>build + up -d</code> を実行すること。</p>
<p># SSH でサーバーに接続してビルド+起動 ssh keinafarm-claude cd /home/claude/alexa-api
# イメージをビルドserver.js 等の変更を反映) sudo docker compose build # コンテナを再作成して起動
sudo docker compose up -d # Traefik を再起動(コンテナ再作成後は必須) sudo docker restart
traefik</p>
<h3>Step 4: 動作確認</h3>
<p># ヘルスチェックWindmill ワーカーコンテナ内から) curl http://alexa_api:3500/health # ログ確認
sudo docker logs alexa_api -f # デバイス一覧確認 curl http://alexa_api:3500/devices</p>
<h3>Cookie だけ更新する場合server.js 変更なし)</h3>
<p># .env をサーバーに転送 scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env
# コンテナを再起動restart で OK → イメージのリビルド不要) ssh keinafarm-claude 'sudo docker
compose -f /home/claude/alexa-api/docker-compose.yml restart' # ※ Traefik
の再起動は不要(コンテナ再作成しないため)</p>
<h2>Traefik 再起動が必要な理由</h2>
<p>docker compose up -d はコンテナを「再作成」するdocker compose restart は既存コンテナを再起動するだけ)。コンテナが再作成されると
Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP を参照したまま 502/504 エラーになる。</p>
<p><strong>対処</strong>: <code>sudo docker restart traefik</code> で Traefik
に新しい IP を再検出させる。</p>
<p>この問題は Traefik の設定で <code>watch: true</code> にすれば自動解消できるが、現状はコンテナ再作成のたびに手動で
Traefik を再起動する運用としている。</p>
<h2>docker-compose.yml の内容</h2>
<p>services: &nbsp;alexa-api: &nbsp; &nbsp;build: . &nbsp; &nbsp;container_name:
alexa_api &nbsp; &nbsp;restart: unless-stopped &nbsp; &nbsp;env_file: &nbsp;
&nbsp; &nbsp;- .env &nbsp; &nbsp;environment: &nbsp; &nbsp; &nbsp;- PORT=3500
&nbsp; &nbsp;networks: &nbsp; &nbsp; &nbsp;- windmill_windmill-internal
&nbsp; &nbsp;# 外部には公開しないWindmill ワーカーから内部ネットワーク経由でのみアクセス) &nbsp; &nbsp;#
デバッグ時は以下のコメントを外す: &nbsp; &nbsp;# ports: &nbsp; &nbsp;# &nbsp; - "127.0.0.1:3500:3500"
networks: &nbsp;windmill_windmill-internal: &nbsp; &nbsp;external: true</p>
<h2>認証方法auth4.js</h2>
<p>Amazon Japan OpenID フローを自前で実装。ローカル PCWindowsでのみ実行する</p>
<p># ローカルPC の alexa-api ディレクトリで実行 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード"
node auth4.js</p>
<p>成功すると alexa-api/.env が生成または更新される。</p>
<p>ログインフローの概要:</p>
<ol>
<li>GET https://www.amazon.co.jp/ap/signin?openid.assoc_handle=amzn_dp_project_dee_jp</li>
<li>hidden フィールドanti-csrftoken-a2z, appActionToken, workflowState 等)を抽出</li>
<li>POST でメール/パスワードを送信</li>
<li>alexa.amazon.co.jp/api/apps/v1/token へのリダイレクトをたどる</li>
<li>取得した Cookieat-acbjp, sess-at-acbjp, sst-acbjp, session-token 等)を .env
に保存</li>
</ol>
<h2>TTS の仕組みserver.js</h2>
<p>alexa-remote2 は使わない直接 API 実装。Endpoints:</p>
<ul>
<li>POST /speak — { device: "デバイス名 or serial", text: "しゃべる内容" }</li>
<li>GET /devices — デバイス一覧</li>
<li>GET /health — ヘルスチェック</li>
</ul>
<p>内部の API 呼び出し順序:</p>
<ol>
<li>GET /api/language → Set-Cookie: csrf=XXXXX を取得(毎リクエストごと)</li>
<li>GET /api/bootstrap → customerId を取得(キャッシュ: 永続A1AE8HXD8IJ61L</li>
<li>GET /api/devices-v2/device → デバイス一覧5分キャッシュ</li>
<li>POST /api/behaviors/preview にシーケンス JSON を送信</li>
</ol>
<p>POST /api/behaviors/preview のボディ構造:</p>
<p>{ &nbsp;behaviorId: "PREVIEW", &nbsp;sequenceJson: JSON.stringify({ &nbsp;
&nbsp;"@type": "com.amazon.alexa.behaviors.model.Sequence", &nbsp; &nbsp;startNode:
{ &nbsp; &nbsp; &nbsp;"@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode",
&nbsp; &nbsp; &nbsp;type: "Alexa.Speak", &nbsp; &nbsp; &nbsp;operationPayload:
{ &nbsp; &nbsp; &nbsp; &nbsp;deviceType: "...", &nbsp; &nbsp; &nbsp; &nbsp;deviceSerialNumber:
"...", &nbsp; &nbsp; &nbsp; &nbsp;customerId: "A1AE8HXD8IJ61L", &nbsp;
&nbsp; &nbsp; &nbsp;locale: "ja-JP", &nbsp; &nbsp;// ← 重要(下記参照) &nbsp;
&nbsp; &nbsp; &nbsp;textToSpeak: "発話内容" &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp;}
&nbsp;}), &nbsp;status: "ENABLED" }</p>
<p>ヘッダーに <code>csrf: XXXXX</code> と Cookie に <code>csrf=XXXXX</code> の両方が必要。Content-Length
は Buffer.byteLength で計算(マルチバイト文字対応)。</p>
<h2>⚠ locale パラメータについて(重要・未解決)</h2>
<figure class="table">
<table>
<thead>
<tr>
<th>locale 値</th>
<th>ローカル PC から</th>
<th>サーバーkeinafarm.netから</th>
</tr>
</thead>
<tbody>
<tr>
<td>""(空文字)</td>
<td>✅ 日本語・英語・漢字全て発話</td>
<td>❌ 英語TTSになり日本語部分が発話されない</td>
</tr>
<tr>
<td>"ja-JP"</td>
<td>❌ 一瞬音が出るだけ(失敗)</td>
<td>❌ 日本語文字が Amazon 側で除去され英字のみ発話</td>
</tr>
</tbody>
</table>
</figure>
<p>現在 server.js では <code>locale: "ja-JP"</code> に設定している。</p>
<p><strong>仮説</strong>: Amazon が海外IPkeinafarm.net = 非日本IPからのリクエストを IP ベースでフィルタリングし、textToSpeak
の日本語文字を除去している。Alexa.TextCommand は同じ問題がない(異なる API パス)。</p>
<p><strong>確認済み事実</strong>: alexa_api の server.js ログには日本語テキストが正しく届いている。除去は
Amazon サーバー側で発生。</p>
<p><strong>次の調査候補</strong>:</p>
<ul>
<li>SSML の &lt;lang xml:lang="ja-JP"&gt; タグで強制的に日本語 TTS を指定できるか</li>
<li>Alexa.TextCommand で「次を読み上げて:{text}」形式が使えるか</li>
<li>ローカルブリッジ方式(ユーザーのローカル PC で小さなプロキシサーバーを動かし、クラウドサーバーからローカル経由で alexa.amazon.co.jp
を叩く)</li>
</ul>
<h2>デバイス一覧Echo デバイスのみ)</h2>
<figure class="table">
<table>
<thead>
<tr>
<th>名前</th>
<th>deviceType</th>
<th>serialNumber</th>
</tr>
</thead>
<tbody>
<tr>
<td>プレハブ</td>
<td>A4ZXE0RM7LQ7A</td>
<td>G0922H085165007R</td>
</tr>
<tr>
<td>リビングエコー1</td>
<td>ASQZWP4GPYUT7</td>
<td>G8M2DB08522600RL</td>
</tr>
<tr>
<td>リビングエコー2</td>
<td>ASQZWP4GPYUT7</td>
<td>G8M2DB08522503WF</td>
</tr>
<tr>
<td>オフィスの右エコー</td>
<td>A4ZXE0RM7LQ7A</td>
<td>G0922H08525302K5</td>
</tr>
<tr>
<td>オフィスの左エコー</td>
<td>A4ZXE0RM7LQ7A</td>
<td>G0922H08525302J9</td>
</tr>
<tr>
<td>寝室のエコー</td>
<td>ASQZWP4GPYUT7</td>
<td>G8M2HN08534302XH</td>
</tr>
</tbody>
</table>
</figure>
<h2>Windmill スクリプトu/admin/alexa_speak</h2>
<p>export async function main(device: string, text: string) { &nbsp;const
res = await fetch("http://alexa_api:3500/speak", { &nbsp; &nbsp;method:
"POST", &nbsp; &nbsp;headers: { "Content-Type": "application/json" }, &nbsp;
&nbsp;body: JSON.stringify({ device, text }), &nbsp;}); &nbsp;if (!res.ok)
throw new Error("alexa-api error " + res.status); &nbsp;return res.json();
}</p>
<p>device はデバイス名日本語またはシリアル番号で指定可能。Windmill ワーカーから <code>http://alexa_api:3500</code> でアクセスwindmill_windmill-internal
ネットワーク経由)。</p>
<h2>Cookie の更新手順</h2>
<p>Cookie は数日〜数週間で期限切れ。切れたら:</p>
<p># 1. ローカル PC で Cookie を取得 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード"
node auth4.js # → alexa-api/.env が更新される # 2. サーバーに .env を転送 scp alexa-api/.env
keinafarm-claude:/home/claude/alexa-api/.env # 3. コンテナを再起動restart で OK、リビルド不要
ssh keinafarm-claude 'sudo docker compose -f /home/claude/alexa-api/docker-compose.yml
restart' # ※ Traefik 再起動は不要(コンテナ再作成なし)</p>
<h2>既知の問題・落とし穴</h2>
<ul>
<li><strong>docker compose restart ≠ リビルド</strong>: server.js を変更しても restart
ではコンテナ内のコードは古いまま。build + up -d が必要。</li>
<li><strong>コンテナ再作成後は Traefik 再起動必須</strong>: up -d でコンテナ再作成すると Docker 内部
IP が変わり Traefik が 502/504 を返す。<code>sudo docker restart traefik</code> で解消。</li>
<li><strong>alexa-remote2 は使えない</strong>: 取得した Cookie 文字列を受け付けない(内部で再認証しようとして失敗)。直接
API 実装が必要。</li>
<li><strong>CSRF トークンは Cookie と ヘッダーの両方に必要</strong>: csrf ヘッダーだけ、または Cookie
だけでは認証失敗。</li>
<li><strong>operationPayload に customerId 必須</strong>: ないと 400 エラー。</li>
<li><strong>レート制限</strong>: 短時間に連続リクエストすると HTTP 429 または 200 で音が出ない。通常の通知用途では問題なし。</li>
<li><strong>git push がブロックされる</strong>: Gitea の pre-receive フック(<code>remote: Gitea: User permission denied for writing</code>)で
push が失敗する。根本原因は未調査。ファイル転送は scp で行っている。</li>
<li><strong>firstRunCompleted: false はデバイス設定の未完了フラグ</strong>: TTS には直接影響しないroot
cause ではなかった)。</li>
</ul>
<h2>サーバー上の運用コマンド一覧</h2>
<p># コンテナ状態確認 sudo docker ps | grep alexa # リアルタイムログ確認 sudo docker logs alexa_api
-f # コンテナ停止 sudo docker compose -f /home/claude/alexa-api/docker-compose.yml
stop # ビルド+起動(コード変更後) cd /home/claude/alexa-api sudo docker compose build
sudo docker compose up -d sudo docker restart traefik # Cookie 更新時(再起動のみ)
sudo docker compose -f /home/claude/alexa-api/docker-compose.yml restart</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<frameset cols="25%,75%">
<frame name="navigation" src="navigation.html">
<frame name="detail" src="Alexa%20TTS%20API%20%E5%AE%9F%E8%A3%85%E8%A8%98%E9%8C%B2%20(2026-03-02.html">
</frameset>
</html>

View File

@@ -0,0 +1,16 @@
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul>
<li><a href="Alexa%20TTS%20API%20%E5%AE%9F%E8%A3%85%E8%A8%98%E9%8C%B2%20(2026-03-02.html"
target="detail">Alexa TTS API 実装記録 (2026-03-02)</a>
</li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,551 @@
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
display: none;
}
/*
* CKEditor 5 (v41.0.0) content styles.
* Generated on Fri, 26 Jan 2024 10:23:49 GMT.
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
*/
:root {
--ck-color-image-caption-background: hsl(0, 0%, 97%);
--ck-color-image-caption-text: hsl(0, 0%, 20%);
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
--ck-color-mention-text: hsl(341, 100%, 30%);
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
--ck-highlight-marker-green: hsl(120, 93%, 68%);
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
--ck-highlight-pen-green: hsl(112, 100%, 27%);
--ck-highlight-pen-red: hsl(0, 85%, 49%);
--ck-image-style-spacing: 1.5em;
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
--ck-todo-list-checkmark-size: 16px;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table .ck-table-resized {
table-layout: fixed;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table table {
overflow: hidden;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table td,
.ck-content .table th {
overflow-wrap: break-word;
position: relative;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table {
margin: 0.9em auto;
display: table;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
height: 100%;
border: 1px double hsl(0, 0%, 70%);
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table td,
.ck-content .table table th {
min-width: 2em;
padding: .4em;
border: 1px solid hsl(0, 0%, 75%);
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table th {
font-weight: bold;
background: hsla(0, 0%, 0%, 5%);
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="rtl"] .table th {
text-align: right;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="ltr"] .table th {
text-align: left;
}
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
.ck-content .table > figcaption {
display: table-caption;
caption-side: top;
word-break: break-word;
text-align: center;
color: var(--ck-color-selector-caption-text);
background-color: var(--ck-color-selector-caption-background);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
position: relative;
clear: both;
padding: 5px 0;
display: flex;
align-items: center;
justify-content: center;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break::after {
content: '';
position: absolute;
border-bottom: 2px dashed hsl(0, 0%, 77%);
width: 100%;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break__label {
position: relative;
z-index: 1;
padding: .3em .6em;
display: block;
text-transform: uppercase;
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
font-size: 0.75em;
font-weight: bold;
color: hsl(0, 0%, 20%);
background: hsl(0, 0%, 100%);
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
.ck-content .media {
clear: both;
margin: 0.9em 0;
display: block;
min-width: 15em;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list {
list-style: none;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list li {
position: relative;
margin-bottom: 5px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list li .todo-list {
margin-top: 5px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input {
-webkit-appearance: none;
display: inline-block;
position: relative;
width: var(--ck-todo-list-checkmark-size);
height: var(--ck-todo-list-checkmark-size);
vertical-align: middle;
border: 0;
left: -25px;
margin-right: -15px;
right: 0;
margin-left: 0;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content[dir=rtl] .todo-list .todo-list__label > input {
left: 0;
margin-right: 0;
right: -25px;
margin-left: -15px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input::before {
display: block;
position: absolute;
box-sizing: border-box;
content: '';
width: 100%;
height: 100%;
border: 1px solid hsl(0, 0%, 20%);
border-radius: 2px;
transition: 250ms ease-in-out box-shadow;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input::after {
display: block;
position: absolute;
box-sizing: content-box;
pointer-events: none;
content: '';
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
border-style: solid;
border-color: transparent;
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
transform: rotate(45deg);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input[checked]::before {
background: hsl(126, 64%, 41%);
border-color: hsl(126, 64%, 41%);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input[checked]::after {
border-color: hsl(0, 0%, 100%);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label .todo-list__label__description {
vertical-align: middle;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
position: absolute;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > input,
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
cursor: pointer;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before {
box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
-webkit-appearance: none;
display: inline-block;
position: relative;
width: var(--ck-todo-list-checkmark-size);
height: var(--ck-todo-list-checkmark-size);
vertical-align: middle;
border: 0;
left: -25px;
margin-right: -15px;
right: 0;
margin-left: 0;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input {
left: 0;
margin-right: 0;
right: -25px;
margin-left: -15px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before {
display: block;
position: absolute;
box-sizing: border-box;
content: '';
width: 100%;
height: 100%;
border: 1px solid hsl(0, 0%, 20%);
border-radius: 2px;
transition: 250ms ease-in-out box-shadow;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after {
display: block;
position: absolute;
box-sizing: content-box;
pointer-events: none;
content: '';
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
border-style: solid;
border-color: transparent;
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
transform: rotate(45deg);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before {
background: hsl(126, 64%, 41%);
border-color: hsl(126, 64%, 41%);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after {
border-color: hsl(0, 0%, 100%);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
position: absolute;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol {
list-style-type: decimal;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol {
list-style-type: lower-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol {
list-style-type: lower-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol {
list-style-type: upper-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol ol {
list-style-type: upper-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul {
list-style-type: disc;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul {
list-style-type: circle;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image {
display: table;
clear: both;
text-align: center;
margin: 0.9em auto;
min-width: 50px;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image img {
display: block;
margin: 0 auto;
max-width: 100%;
min-width: 100%;
height: auto;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline {
/*
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
* This strange behavior does not happen with inline-flex.
*/
display: inline-flex;
max-width: 100%;
align-items: flex-start;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture {
display: flex;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture,
.ck-content .image-inline img {
flex-grow: 1;
flex-shrink: 1;
max-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content img.image_resized {
height: auto;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized {
max-width: 100%;
display: block;
box-sizing: border-box;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized img {
width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized > figcaption {
display: block;
}
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
.ck-content .image > figcaption {
display: table-caption;
caption-side: bottom;
word-break: break-word;
color: var(--ck-color-image-caption-text);
background-color: var(--ck-color-image-caption-background);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left,
.ck-content .image-style-block-align-right {
max-width: calc(100% - var(--ck-image-style-spacing));
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left,
.ck-content .image-style-align-right {
clear: none;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-side {
float: right;
margin-left: var(--ck-image-style-spacing);
max-width: 50%;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left {
float: left;
margin-right: var(--ck-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-center {
margin-left: auto;
margin-right: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-right {
float: right;
margin-left: var(--ck-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-right {
margin-right: 0;
margin-left: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left {
margin-left: 0;
margin-right: auto;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content p + .image-style-align-left,
.ck-content p + .image-style-align-right,
.ck-content p + .image-style-side {
margin-top: 0;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left,
.ck-content .image-inline.image-style-align-right {
margin-top: var(--ck-inline-image-style-spacing);
margin-bottom: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left {
margin-right: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-right {
margin-left: var(--ck-inline-image-style-spacing);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content blockquote {
overflow: hidden;
padding-right: 1.5em;
padding-left: 1.5em;
margin-left: 0;
margin-right: 0;
font-style: italic;
border-left: solid 5px hsl(0, 0%, 80%);
}
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content[dir="rtl"] blockquote {
border-left: 0;
border-right: solid 5px hsl(0, 0%, 80%);
}
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
.ck-content code {
background-color: hsla(0, 0%, 78%, 0.3);
padding: .15em;
border-radius: 2px;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-tiny {
font-size: .7em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-small {
font-size: .85em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-big {
font-size: 1.4em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-huge {
font-size: 1.8em;
}
/* @ckeditor/ckeditor5-mention/theme/mention.css */
.ck-content .mention {
background: var(--ck-color-mention-background);
color: var(--ck-color-mention-text);
}
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
.ck-content hr {
margin: 15px 0;
height: 4px;
background: hsl(0, 0%, 87%);
border: 0;
}
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre {
padding: 1em;
color: hsl(0, 0%, 20.8%);
background: hsla(0, 0%, 78%, 0.3);
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
text-align: left;
direction: ltr;
tab-size: 4;
white-space: pre-wrap;
font-style: normal;
min-width: 200px;
}
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre code {
background: unset;
padding: 0;
border-radius: 0;
}
@media print {
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
padding: 0;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break::after {
display: none;
}
}