From 67cc3da6c86286bbfbff27eeaffc2386c5d6fc64 Mon Sep 17 00:00:00 2001 From: Kenichiro NOGI Date: Mon, 13 Jul 2026 19:02:04 +0900 Subject: [PATCH] up --- .../MSS回覧板システム/.claude/settings.json | 6 +- .../助っ人クラウド/.claude/settings.json | 11 + .../.claude/skills/pleasanter-site-spec/SKILL.md | 223 + ClaudePleasanter/助っ人クラウド/.gitignore | 9 + .../助っ人クラウド/.vscode/settings.json | 3 + ClaudePleasanter/助っ人クラウド/README.md | 101 + .../助っ人クラウド/apply-site-config.js | 156 + .../助っ人クラウド/build-desired-config.js | 139 + .../助っ人クラウド/config.example.json | 6 + .../助っ人クラウド/extract-site-config.js | 195 + .../助っ人クラウド/get-site-config.js | 166 + ClaudePleasanter/助っ人クラウド/md-to-pdf.js | 233 + .../助っ人クラウド_2026_07_13 18_41_58.json | 5579 +++++++++++++++++ .../営業積算システム/.claude/settings.json | 8 + .../.claude/skills/pleasanter-site-spec/SKILL.md | 266 + ClaudePleasanter/営業積算システム/.gitignore | 16 + .../営業積算システム/.vscode/settings.json | 9 + ClaudePleasanter/営業積算システム/README.md | 115 + .../営業積算システム/apply-site-config.js | 156 + .../営業積算システム/build-desired-config.js | 139 + .../営業積算システム/config.example.json | 6 + .../docs/site-335410_①物件基本情報 Ver0.3_overview.md | 56 + .../site-335410_①物件基本情報 Ver0.3_overview.pdf | Bin 0 -> 132980 bytes .../docs/site-335410_①物件基本情報 Ver0.3_spec.md | 229 + ...site-335411_②問い合わせ内容管理 Ver0.3_overview.md | 71 + ...ite-335411_②問い合わせ内容管理 Ver0.3_overview.pdf | Bin 0 -> 124217 bytes .../site-335411_②問い合わせ内容管理 Ver0.3_spec.md | 308 + .../docs/site-335450_遅延理由テンプレ管理_overview.md | 26 + .../site-335450_遅延理由テンプレ管理_overview.pdf | Bin 0 -> 82972 bytes .../docs/site-335450_遅延理由テンプレ管理_spec.md | 72 + .../営業積算システム/extract-site-config.js | 195 + .../generate-site-documentation-output.txt | 232 + .../generate-site-documentation.js | 576 ++ .../営業積算システム/get-site-config.js | 166 + .../営業積算システム/md-to-pdf.js | 233 + .../docs_old/site-160926_稟議コード管理_overview.md | 45 + .../docs_old/site-160926_稟議コード管理_overview.pdf | Bin 0 -> 97573 bytes .../docs_old/site-319014_新・稟議申請_overview.md | 14 + .../docs_old/site-319014_新・稟議申請_overview.pdf | Bin 0 -> 64021 bytes .../docs_old/site-319015_新・稟議申請書_overview.md | 141 + .../docs_old/site-319015_新・稟議申請書_overview.pdf | Bin 0 -> 222051 bytes .../docs_old/site-319016_【ISO】稟議管理_overview.md | 203 + .../docs_old/site-319016_【ISO】稟議管理_overview.pdf | Bin 0 -> 253801 bytes .../site-319017_【ISO】部署担当者別稟議集計_overview.md | 40 + ...site-319017_【ISO】部署担当者別稟議集計_overview.pdf | Bin 0 -> 89555 bytes .../docs_old/site-319020_稟議項目_overview.md | 16 + .../docs_old/site-319020_稟議項目_overview.pdf | Bin 0 -> 56736 bytes .../docs_old/site-319021_稟議通知管理_overview.md | 22 + .../docs_old/site-319021_稟議通知管理_overview.pdf | Bin 0 -> 66395 bytes .../docs_old/site-319022_【部署別】稟議改善_overview.md | 43 + .../site-319022_【部署別】稟議改善_overview.pdf | Bin 0 -> 100817 bytes .../docs_old/site-319027_事前稟議一覧_overview.md | 34 + .../docs_old/site-319027_事前稟議一覧_overview.pdf | Bin 0 -> 80344 bytes .../docs_old/site-335730_組織エリアマスタ_overview.md | 19 + .../docs_old/site-335730_組織エリアマスタ_overview.pdf | Bin 0 -> 60859 bytes PleasanterSystem/dbbackup.zip | Bin 0 -> 36712 bytes PleasanterSystem/dbbackup/.gitignore | 7 + PleasanterSystem/dbbackup/HANDOFF.md | 67 + PleasanterSystem/dbbackup/SPEC.md | 192 + PleasanterSystem/dbbackup/backup.js | 216 + PleasanterSystem/dbbackup/backup.sh | 48 + PleasanterSystem/dbbackup/get-token.js | 63 + PleasanterSystem/dbbackup/lib/env.js | 61 + .../dbbackup/lib/lineworksAuth.js | 115 + .../dbbackup/lib/lineworksDrive.js | 276 + .../dbbackup/lib/lineworksUserAuth.js | 120 + PleasanterSystem/dbbackup/lib/logger.js | 62 + PleasanterSystem/dbbackup/lib/pgDump.js | 118 + PleasanterSystem/dbbackup/lib/testDb.js | 71 + .../dbbackup/lineworks-authorize.js | 94 + PleasanterSystem/dbbackup/package.json | 16 + PleasanterSystem/dbbackup/sftp.sh | 31 + dbbackup-full.zip | Bin 0 -> 57495 bytes dbbackup/.gitignore | 7 + dbbackup/HANDOFF.md | 67 + dbbackup/SPEC.md | 354 ++ dbbackup/backup.js | 206 + dbbackup/get-token.js | 63 + dbbackup/lib/backupLocator.js | 93 + dbbackup/lib/configBackup.js | 61 + dbbackup/lib/configRestore.js | 91 + dbbackup/lib/env.js | 61 + dbbackup/lib/lineworksAuth.js | 115 + dbbackup/lib/lineworksDrive.js | 317 + dbbackup/lib/lineworksUserAuth.js | 120 + dbbackup/lib/logUpload.js | 46 + dbbackup/lib/logger.js | 62 + dbbackup/lib/pgDump.js | 121 + dbbackup/lib/pgRestore.js | 39 + dbbackup/lib/serviceControl.js | 47 + dbbackup/lib/testDb.js | 91 + dbbackup/lineworks-authorize.js | 94 + dbbackup/package.json | 17 + dbbackup/restore.js | 450 ++ dotnet-install.sh | 1887 ++++++ 95 files changed, 16248 insertions(+), 1 deletion(-) create mode 100644 ClaudePleasanter/助っ人クラウド/.claude/settings.json create mode 100644 ClaudePleasanter/助っ人クラウド/.claude/skills/pleasanter-site-spec/SKILL.md create mode 100644 ClaudePleasanter/助っ人クラウド/.gitignore create mode 100644 ClaudePleasanter/助っ人クラウド/.vscode/settings.json create mode 100644 ClaudePleasanter/助っ人クラウド/README.md create mode 100644 ClaudePleasanter/助っ人クラウド/apply-site-config.js create mode 100644 ClaudePleasanter/助っ人クラウド/build-desired-config.js create mode 100644 ClaudePleasanter/助っ人クラウド/config.example.json create mode 100644 ClaudePleasanter/助っ人クラウド/extract-site-config.js create mode 100644 ClaudePleasanter/助っ人クラウド/get-site-config.js create mode 100644 ClaudePleasanter/助っ人クラウド/md-to-pdf.js create mode 100644 ClaudePleasanter/助っ人クラウド/助っ人クラウド_2026_07_13 18_41_58.json create mode 100644 ClaudePleasanter/営業積算システム/.claude/settings.json create mode 100644 ClaudePleasanter/営業積算システム/.claude/skills/pleasanter-site-spec/SKILL.md create mode 100644 ClaudePleasanter/営業積算システム/.gitignore create mode 100644 ClaudePleasanter/営業積算システム/.vscode/settings.json create mode 100644 ClaudePleasanter/営業積算システム/README.md create mode 100644 ClaudePleasanter/営業積算システム/apply-site-config.js create mode 100644 ClaudePleasanter/営業積算システム/build-desired-config.js create mode 100644 ClaudePleasanter/営業積算システム/config.example.json create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335410_①物件基本情報 Ver0.3_overview.md create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335410_①物件基本情報 Ver0.3_overview.pdf create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335410_①物件基本情報 Ver0.3_spec.md create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335411_②問い合わせ内容管理 Ver0.3_overview.md create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335411_②問い合わせ内容管理 Ver0.3_overview.pdf create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335411_②問い合わせ内容管理 Ver0.3_spec.md create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335450_遅延理由テンプレ管理_overview.md create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335450_遅延理由テンプレ管理_overview.pdf create mode 100644 ClaudePleasanter/営業積算システム/docs/site-335450_遅延理由テンプレ管理_spec.md create mode 100644 ClaudePleasanter/営業積算システム/extract-site-config.js create mode 100644 ClaudePleasanter/営業積算システム/generate-site-documentation-output.txt create mode 100644 ClaudePleasanter/営業積算システム/generate-site-documentation.js create mode 100644 ClaudePleasanter/営業積算システム/get-site-config.js create mode 100644 ClaudePleasanter/営業積算システム/md-to-pdf.js create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-160926_稟議コード管理_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-160926_稟議コード管理_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319014_新・稟議申請_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319014_新・稟議申請_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319015_新・稟議申請書_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319015_新・稟議申請書_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319016_【ISO】稟議管理_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319016_【ISO】稟議管理_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319017_【ISO】部署担当者別稟議集計_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319017_【ISO】部署担当者別稟議集計_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319020_稟議項目_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319020_稟議項目_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319021_稟議通知管理_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319021_稟議通知管理_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319022_【部署別】稟議改善_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319022_【部署別】稟議改善_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319027_事前稟議一覧_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-319027_事前稟議一覧_overview.pdf create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-335730_組織エリアマスタ_overview.md create mode 100644 ClaudePleasanter/新・稟議申請システム/docs_old/site-335730_組織エリアマスタ_overview.pdf create mode 100644 PleasanterSystem/dbbackup.zip create mode 100644 PleasanterSystem/dbbackup/.gitignore create mode 100644 PleasanterSystem/dbbackup/HANDOFF.md create mode 100644 PleasanterSystem/dbbackup/SPEC.md create mode 100644 PleasanterSystem/dbbackup/backup.js create mode 100644 PleasanterSystem/dbbackup/backup.sh create mode 100644 PleasanterSystem/dbbackup/get-token.js create mode 100644 PleasanterSystem/dbbackup/lib/env.js create mode 100644 PleasanterSystem/dbbackup/lib/lineworksAuth.js create mode 100644 PleasanterSystem/dbbackup/lib/lineworksDrive.js create mode 100644 PleasanterSystem/dbbackup/lib/lineworksUserAuth.js create mode 100644 PleasanterSystem/dbbackup/lib/logger.js create mode 100644 PleasanterSystem/dbbackup/lib/pgDump.js create mode 100644 PleasanterSystem/dbbackup/lib/testDb.js create mode 100644 PleasanterSystem/dbbackup/lineworks-authorize.js create mode 100644 PleasanterSystem/dbbackup/package.json create mode 100644 PleasanterSystem/dbbackup/sftp.sh create mode 100644 dbbackup-full.zip create mode 100644 dbbackup/.gitignore create mode 100644 dbbackup/HANDOFF.md create mode 100644 dbbackup/SPEC.md create mode 100644 dbbackup/backup.js create mode 100644 dbbackup/get-token.js create mode 100644 dbbackup/lib/backupLocator.js create mode 100644 dbbackup/lib/configBackup.js create mode 100644 dbbackup/lib/configRestore.js create mode 100644 dbbackup/lib/env.js create mode 100644 dbbackup/lib/lineworksAuth.js create mode 100644 dbbackup/lib/lineworksDrive.js create mode 100644 dbbackup/lib/lineworksUserAuth.js create mode 100644 dbbackup/lib/logUpload.js create mode 100644 dbbackup/lib/logger.js create mode 100644 dbbackup/lib/pgDump.js create mode 100644 dbbackup/lib/pgRestore.js create mode 100644 dbbackup/lib/serviceControl.js create mode 100644 dbbackup/lib/testDb.js create mode 100644 dbbackup/lineworks-authorize.js create mode 100644 dbbackup/package.json create mode 100644 dbbackup/restore.js create mode 100644 dotnet-install.sh diff --git a/ClaudePleasanter/MSS回覧板システム/.claude/settings.json b/ClaudePleasanter/MSS回覧板システム/.claude/settings.json index 77cc8788..3d8cdb94 100644 --- a/ClaudePleasanter/MSS回覧板システム/.claude/settings.json +++ b/ClaudePleasanter/MSS回覧板システム/.claude/settings.json @@ -1,7 +1,11 @@ { "permissions": { "allow": [ - "Bash(node -c get-site-config.js)" + "Bash(node -c get-site-config.js)", + "Bash(node get-site-config.js \"C:\\\\Users\\\\KA5AC~1.NOG\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\c--Users-k-nogi--GitHub-ken-nogi-ClaudePleasanter-MSS-------\\\\4612346a-d27e-4254-ab27-863e0d44e52a\\\\scratchpad\\\\config_492045.json\")", + "Bash(echo \"exit:$?\")", + "Bash(node get-site-config.js \"C:\\\\Users\\\\KA5AC~1.NOG\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\c--Users-k-nogi--GitHub-ken-nogi-ClaudePleasanter-MSS-------\\\\4612346a-d27e-4254-ab27-863e0d44e52a\\\\scratchpad\\\\config_475384.json\")", + "Bash(node get-site-config.js \"C:\\\\Users\\\\KA5AC~1.NOG\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\c--Users-k-nogi--GitHub-ken-nogi-ClaudePleasanter-MSS-------\\\\4612346a-d27e-4254-ab27-863e0d44e52a\\\\scratchpad\\\\config_492169.json\")" ] } } diff --git a/ClaudePleasanter/助っ人クラウド/.claude/settings.json b/ClaudePleasanter/助っ人クラウド/.claude/settings.json new file mode 100644 index 00000000..3d8cdb94 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/.claude/settings.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "allow": [ + "Bash(node -c get-site-config.js)", + "Bash(node get-site-config.js \"C:\\\\Users\\\\KA5AC~1.NOG\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\c--Users-k-nogi--GitHub-ken-nogi-ClaudePleasanter-MSS-------\\\\4612346a-d27e-4254-ab27-863e0d44e52a\\\\scratchpad\\\\config_492045.json\")", + "Bash(echo \"exit:$?\")", + "Bash(node get-site-config.js \"C:\\\\Users\\\\KA5AC~1.NOG\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\c--Users-k-nogi--GitHub-ken-nogi-ClaudePleasanter-MSS-------\\\\4612346a-d27e-4254-ab27-863e0d44e52a\\\\scratchpad\\\\config_475384.json\")", + "Bash(node get-site-config.js \"C:\\\\Users\\\\KA5AC~1.NOG\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\c--Users-k-nogi--GitHub-ken-nogi-ClaudePleasanter-MSS-------\\\\4612346a-d27e-4254-ab27-863e0d44e52a\\\\scratchpad\\\\config_492169.json\")" + ] + } +} diff --git a/ClaudePleasanter/助っ人クラウド/.claude/skills/pleasanter-site-spec/SKILL.md b/ClaudePleasanter/助っ人クラウド/.claude/skills/pleasanter-site-spec/SKILL.md new file mode 100644 index 00000000..5fef4226 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/.claude/skills/pleasanter-site-spec/SKILL.md @@ -0,0 +1,223 @@ +--- +name: pleasanter-site-spec +description: Pleasanterのサイト設定JSON(get-site-config.js / api-site-get の getsite レスポンス)から、SiteSettingsを読み解いたMarkdown仕様書を生成し、Scripts/Styles/ServerScripts/Processesの抽出・編集・反映(extract-site-config.js / build-desired-config.js / apply-site-config.js)を行う。また、技術仕様書とは別に業務担当者向けの概要書(docs/site-{SiteId}_overview.md)を作成し、md-to-pdf.jsでPDF化する。「仕様書を作って」「サイト仕様書」「SiteSettingsをドキュメント化」「スクリプトを抜き出して」「プロセス設定を直して反映して」「概要書を作って」「PDFにして」「site-spec」等の依頼で使う。 +--- + +# Pleasanter サイト仕様書生成 + +`./configs/site-{SiteId}_latest.json`(`getsite` APIレスポンス、`Response.Data` 配下にサイト情報)を読み解き、 +`./configs/site-{SiteId}_spec.md` として日本語のMarkdown仕様書を出力する。 + +## 入力の特定 + +1. ユーザーがJSONファイルパスやSiteIdを指定した場合はそれを使う。 +2. 指定がなければ `./configs/site-*_latest.json` を探す。複数ある場合はユーザーに確認する。 +3. `./configs/site-{SiteId}_latest.json` が無ければ、先に `node get-site-config.js` の実行を提案する + (エンドポイントは `/api/items/{SiteId}/getsite` を使うこと。`/api/items/{SiteId}/get` はアイテム一覧取得APIであり別物なので注意)。 + +## 出力ファイル + +`./configs/site-{SiteId}_spec.md`(既存があれば上書きしてよい)。 + +## 仕様書の構成(この順番・見出しレベルを踏襲する) + +```markdown +# サイト仕様書:{Title}(SiteId: {SiteId}) + +- 取得元データ: [site-{SiteId}_latest.json](./site-{SiteId}_latest.json) +- 取得日時: {UpdatedTime}(サイト更新日時)/設定バージョン `Ver: {Ver}` + +## 1. サイト基本情報 +(TenantId, Title, ReferenceType, ParentId, InheritPermission, Publish, + DisableCrossSearch, Creator/Updator, CreatedTime, SiteSettings.Version を表形式で) + +## 2. アクセス権限(Permissions) +(Permissions配列を 種別/対象ID/権限レベル の表に分解。 + 「権限値はビットフラグの加算方式のため正確な意味は環境のロール定義に依存する」旨を注記) + +## 3. 画面構成 +### 3.1 一覧画面(GridColumns) +(GridColumns配列を 順番/列名/表示ラベル の表に。ラベルはColumns配列から解決、 + 無ければ「項目種別の判定ルール」のデフォルト名を使う) + +### 3.2 編集画面レイアウト(EditorColumnHash / Sections) +(EditorColumnHashの各タブ配列を 順番/項目名(内部名)/ラベル名 の表に。 + `_Section-N` はラベル名欄に「―(セクション「{LabelText}」開始)」と記載。 + 続けてSections配列を Id/ラベル/AllowExpand/Expand の表で示す) + +### 3.3 タイトル表示(TitleColumns / TitleSeparator) +(TitleColumns配列をTitleSeparatorで連結した式を明記) + +## 4. 項目定義(Columns) +(Columns配列を 列名(内部)/種別/表示ラベル/説明・入力ガイド/備考 の表に。 + 種別は下記「項目種別の判定ルール」で推定。 + 備考にはNoWrap, FieldCss, ExtendedHtmlAfterControlの有無などを記載) + +### 4.1 項目ごとの補足HTML(ExtendedHtmlAfterControl) +(該当項目があれば 列名/色(styleやclassから読み取れれば)/内容 の表。無ければこの節は省略) + +## 5. 作成・更新権限(フィールド単位) +(PermissionForCreating / PermissionForUpdating を表で。無ければ「設定なし」と明記) + +## 6. 集計設定(Aggregations) +(存在する場合のみ。Id/GroupBy/Type/Target を表で) + +## 7. 他サイト連携(Links / ルックアップ) +(存在する場合のみ。起点列/参照先SiteId/参照先の値→自コピー先/JsonFormat を表で。 + 「参照先サイトの{From}列の値を、このサイトの{To}列にコピーする」という関係性を文章でも補足) + +## 8. プロセス設定(Processes) +(存在する場合のみ。定義ファイルへのリンク configs/site-{SiteId}_{サイト名}/processes.json を明記した上で、 + Id/Name・DisplayName/実行条件(CurrentStatus→ChangedStatus)/動作(OnClick等)/成功メッセージ を表で) + +## 9. スタイル(Styles) +(存在する場合のみ。Id/Title/適用範囲(New/Edit/Index等のフラグ)/ファイルへのリンクを表で。 + CSS本体はMarkdownに貼らず、抽出済みファイルへのリンクのみとする) + +## 10. スクリプト(Scripts)/サーバースクリプト(ServerScripts) +(存在する場合のみ。Id/Title/適用画面/ファイルへのリンクを表で。 + スクリプト本体はMarkdownに貼らず、抽出済みファイルへのリンクのみとする。 + 概要は「何をするスクリプトか」を1〜2行で要約。 + 本文中にAPIキー・Webhook URL・パスワード等の秘密情報らしき文字列(`API_KEY`, `SECRET`, `Bearer `, + URLに埋め込まれたトークン等)を見つけた場合は、値そのものをMarkdownに転記せず、 + 「気になる点」に必ずセキュリティ上の指摘として記載する) + +## 11. その他設定 +(NoDisplayIfReadOnly, HideLink, SectionLatestId, 各種ガイド文の設定有無, Commentsの使用有無など) + +--- + +## 気になる点(レビュー観点) +(プレースホルダーらしき文言、権限設定の偏り、項目単位の権限の抜け、 + 未設定のガイド文など、気づいた点を箇条書きで) +``` + +## 項目種別の判定ルール(Columns内 ColumnName のプレフィックスから推定) + +| プレフィックス/名前 | 種別 | +|---|---| +| ClassA〜Z | 文字列(分類) | +| Class001〜040等(3桁連番) | 文字列(拡張分類)。`Hide: true`の場合が多く、他項目のルックアップ先の値を裏で保持する用途が多い | +| NumA〜Z | 数値 | +| DateA〜Z | 日付 | +| DescriptionA〜Z | 説明(複数行文字列) | +| CheckA〜Z | チェックボックス | +| AttachmentsA〜Z | 添付ファイル | +| Owner, Manager, Assignee等 | ユーザー選択 | +| Status | 状態 | +| {対象}Id(ResultId, IssueId等) | 自動採番ID | +| Title | タイトル | +| Comments | コメント欄 | + +## 注意事項 + +- SiteSettingsに存在しない項目・空の設定(例: `ClassHash`, `NumHash` などのラベル定義ハッシュが `{}`)は + 「標準ラベルのまま」として仕様書に明記し、存在しないかのように省略しない。 +- 表形式を基本とし、箇条書きより表を優先する(今回のレビューで「編集画面レイアウトは項目名とラベル名を併記」との + 指摘を受けた経緯があるため、内部名とラベル名は必ず併記する)。 +- 出力言語は日本語。 +- 複数サイトが関連する仕様書(「関連するテーブルと合わせて」等の依頼)を作る場合は、対象サイトの`Links`設定だけでなく、 + ServerScripts内で`items.Get(masterTableId)`のように直接SiteIdをハードコードして参照している「正式なLinks設定に現れない + 暗黙的なテーブル参照」も洗い出し、別セクションとして明記する。あわせて`ParentId`を辿ってサイトツリー上の位置関係も + 簡潔に図示するとレビューしやすい。 + +## 概要書(業務担当者向け機能説明)の生成 + +技術仕様書(spec.md)とは別に、「概要書を作って」「機能的な説明がほしい」等の依頼があった場合は、 +`ColumnName`(ClassA, DateB, Class001等の内部コード)やJSON設定値・スクリプト実装といった技術的要素を一切排除し、 +ラベル名と業務フローのみで構成した概要書を作成する。 + +### 出力ファイル + +`./docs/site-{SiteId}_overview.md`(`configs/`ではなく`docs/`フォルダに置く。`configs/`は秘密情報を含み得るため +`.gitignore`済みだが、`docs/`配下の概要書・PDFは共有物としてGit管理対象にする想定)。 +`docs/`フォルダが無ければ作成する。 + +### 概要書の構成 + +```markdown +# 概要書:{Title} + +- 技術仕様(項目のシステム上の名称・設定値等)は [{spec.mdへのリンク}](../configs/site-{SiteId}_spec.md) を参照。 + 本書は業務担当者向けに、機能面のみを平易に説明するものです。 + +## 1. これは何か +(業務的にこのサイトが何のためのものかを2〜4文で) + +## 2. 登場する情報(入力項目) +(分類/項目(ラベル名のみ)/内容 の表。ColumnNameは記載しない。Hide項目でも + 業務上意味のあるもの(自動反映される裏項目等)は「内容」欄で触れる) + +## 3. 業務の流れ(ボタン操作) +(操作/状態の変化(ラベル値同士、例:「新規作成→図確回覧」)/説明 の表。 + Processesの内部処理・スクリプト名には触れず、「何が起きるか」を業務言葉で説明する) + +## 4. 通知される場面 +(Notificationsを「◯◯時に誰に何が届くか」という箇条書きに変換。メール本文・件名の技術的書式は転記しない) + +## 5. 関連する仕組み(マスタ連携) +(Links・暗黙的テーブル参照を「◯◯マスタ:何を自動反映するか」という業務目線の箇条書きに変換。 + SiteIdやColumnNameは書かず、サイト名(Title)とその役割のみ記載。 + 兄弟サイト(同じ設計パターンの別サイト)があれば、違いを1文で触れる) +``` + +### 作成時の注意 + +- 表内の「項目」列は必ずラベル名(LabelText)のみ。ラベル未設定の項目は概要書には載せない(業務上意味を説明できないため)。 +- Statusの遷移は内部コード(100/300/900等)を書かず、ラベル値(新規作成/完了 等)同士の矢印で表現する。 +- スクリプト内で見つけた秘密情報(APIキー等)は概要書には一切転記しない(そもそも概要書はスクリプト実装に触れない)。 +- 複数サイト分をまとめて依頼された場合、共通する業務フローは重複して書いてよいが、各サイト固有の違い(段階数の違い、 + 追加の管理項目など)を各概要書の末尾で明示し、他サイトとの差分が一目で分かるようにする。 + +## PDF化 + +概要書(または他のMarkdown)をPDF化する依頼があった場合は、プロジェクトルートの `md-to-pdf.js` を使う。 + +``` +node md-to-pdf.js ./docs/site-{SiteId}_overview.md +``` + +- 追加npmパッケージ非依存(本プロジェクトに`package.json`が無いことに合わせた設計)。Markdown→HTML変換は + スクリプト内の簡易パーサ(見出し/表/太字/リンク/箇条書き/水平線/段落)で行い、PDF化はOS標準のMicrosoft Edge + (無ければGoogle Chrome)のヘッドレス印刷機能を使う。 +- 出力は入力と同じフォルダに`{同名}.pdf`(中間HTMLは一時フォルダに作成し、変換後に自動削除)。 +- 複数ファイルを一度に渡せる(`node md-to-pdf.js ./docs/*.md`)。 +- **既知の落とし穴**: フォルダパスに`#`を含む場合(例: `#GitHub`)、素朴に`file://` + パス文字列を連結すると + `#`以降がURLフラグメントと誤認識され、無関係なページが開かれてPDFの中身が空/別物になる。 + `md-to-pdf.js`は`url.pathToFileURL()`で正しくエンコードしているため通常は問題にならないが、 + 同種のブラウザ自動化コードを新たに書く場合は同じ罠に注意する。 + +## 関連ツール:Scripts/Styles/ServerScripts/Processesの抽出・編集・反映 + +仕様書化とは別に、「スクリプトを個別ファイルに分けて」「プロセス設定を直して反映して」等の依頼では、 +以下のツールチェーンを使う(詳細は `README.md` 参照)。 + +1. `node get-site-config.js` … `configs/site-{SiteId}_latest.json` を取得し、続けて自動的に抽出も行う + (`extract-site-config.js` が内部から呼ばれ、`configs/site-{SiteId}_{サイト名}/` に以下を分割保存する。未取得なら先にこれを実行) + - `scripts/{Id}_{Title}.js`(クライアントスクリプト本体) + - `styles/{Id}_{Title}.css`(スタイル本体) + - `serverscripts/{Id}_{Title}.js`(サーバースクリプト本体) + - `html/{GuideName}.html`(GridGuide等のガイドHTML。SiteSettingsではなくサイトデータ直下の項目) + - `processes.json`(`SiteSettings.Processes` の定義ファイル、1ファイルにまとめる。分割しない) + - `manifest.json`(上記ファイルとId/Titleの対応表。再構成に必須) + - 抽出だけをやり直したい場合(`latest.json`は既に取得済み)は `node extract-site-config.js` を単独実行してもよい +2. ユーザーの指示に従い、`scripts/*.js` 等の中身を直接編集する(`manifest.json` のId/メタ情報は変更しない)。 + 新規追加の場合は `manifest.json` に対応エントリ(`Id`は既存と重複しない値、`File`名)を追加してからファイルを作成する。 +3. `node build-desired-config.js site-{SiteId}` … 分割ファイルを再構成し `desired_config.json` を生成(送信なし)。 + フォルダ名はサイト名まで含む正式名(`site-{SiteId}_{サイト名}`)だが、`site-{SiteId}` の前方一致指定でも + `configs/`配下から自動解決される +4. `node apply-site-config.js` … ドライラン確認(差分・送信Body・curlコマンド表示のみ、送信なし) +5. 内容をユーザーに確認してもらい、問題なければ表示されたcurlコマンドをユーザー自身が手動実行する + +### 厳守事項(安全設計) + +- **`apply-site-config.js` は常にドライラン。Claude自身がAPIへ実送信することはしない** + (`ENABLE_ACTUAL_SEND` を有効化する改修や、curlコマンドの代理実行も行わない)。 + 反映の最終実行はユーザー自身に委ねる。 +- `build-desired-config.js` が作る `desired_config.json` は `Mode:"partial"`(`updatesitesettings`)専用。 + Scripts/Styles/ServerScripts/Processes以外のサイト設定には触れない。 +- ガイドHTML(`html/*.html`)は `updatesitesettings` では反映できない(サイト直下の項目のため)。 + 反映が必要なら `Mode:"full"`(`updatesite`)用の別JSONが要ることをユーザーに伝える。自動生成はしない。 +- 抽出したスクリプト・サーバースクリプトの中に **APIキーやWebhook URL等の秘密情報がハードコードされている場合がある** + (実例あり)。抽出・編集作業でこれらの値を見つけたら、チャット上にそのまま貼り付けず、ユーザーに直接ファイルを確認してもらう。 + また `configs/` フォルダと `config.json` は `.gitignore` 済みだが、誤ってコミットしないよう注意喚起する。 diff --git a/ClaudePleasanter/助っ人クラウド/.gitignore b/ClaudePleasanter/助っ人クラウド/.gitignore new file mode 100644 index 00000000..6a07ef70 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/.gitignore @@ -0,0 +1,9 @@ +# Pleasanter API接続情報(実APIキーを含む) +config.json + +# サイト構成の取得結果・抽出結果(サイトのスクリプト等に秘密情報が +# ハードコードされている場合があるため、フォルダごと除外) +configs/ + +# 反映用に生成される希望構成(機密を含むスクリプト等を含み得る) +desired_config.json diff --git a/ClaudePleasanter/助っ人クラウド/.vscode/settings.json b/ClaudePleasanter/助っ人クラウド/.vscode/settings.json new file mode 100644 index 00000000..965f5b8a --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "markdown-pdf.executablePath": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" +} \ No newline at end of file diff --git a/ClaudePleasanter/助っ人クラウド/README.md b/ClaudePleasanter/助っ人クラウド/README.md new file mode 100644 index 00000000..606b9e65 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/README.md @@ -0,0 +1,101 @@ +# プリザンター サイト構成 取得・レビュー・反映ツール + +## 全体の流れ + +``` +① node get-site-config.js + → configs/site-{SiteId}_latest.json に構成を保存 + → 続けて自動的に extract-site-config.js が実行され、 + Scripts / Styles / ServerScripts / Processes / ガイドHTML が + configs/site-{SiteId}_{サイト名}/ 配下に個別ファイルとして分割保存される + → config.json の SiteId をカンマ区切りにすると、複数サイトをまとめて①の処理でループ取得する + +② その latest.json をClaudeのチャットにアップロード + → Claudeが構成をレビュー・仕様書化(configs/site-{SiteId}_spec.md) + +③ configs/site-{SiteId}_{サイト名}/ 配下のファイルを直接編集、 + またはClaudeに「このスクリプトを◯◯に直して」等と修正を指示 + +④ node build-desired-config.js site-{SiteId} + → 分割ファイルを再構成し、desired_config.json を生成(送信は行わない) + +⑤ node apply-site-config.js + → 実際には送信せず、差分・送信予定Body・curlコマンドのみ表示(常にドライラン) + +⑥ 内容を確認し、問題なければ表示されたcurlコマンドを手動実行 + +--- (任意:非エンジニア向けドキュメントが必要な場合) --- + +⑦ Claudeに「概要書を作って」と依頼 + → 技術仕様書とは別に、ColumnName等の内部コードを含まない業務向け概要書を + docs/site-{SiteId}_overview.md として生成(configs/とは異なりGit管理対象) + +⑧ node md-to-pdf.js ./docs/site-{SiteId}_overview.md + → 同名の docs/site-{SiteId}_overview.pdf を生成 +``` + +`extract-site-config.js` は `node get-site-config.js` の中から自動実行されるため、通常は個別に実行する必要はありません +(取得済みの `latest.json` から抽出をやり直したい場合のみ、単独で `node extract-site-config.js` を実行してください)。 + +## 初回セットアップ + +1. `config.example.json` を `config.json` にコピー +2. `BaseUrl` / `SiteId` / `ApiKey` を実環境の値に書き換え + - `SiteId` は単一サイトなら数値(例: `12345`)、複数サイトをまとめて取得したい場合は + カンマ区切りの文字列(例: `"12345,23456,34567"`)で指定できます + - APIキーの発行方法: https://pleasanter.org/manual/api-key + - APIキー発行にはテナント管理者権限が必要です(サイト設定の更新系APIを使う場合) +3. Node.js 18以降がインストールされていることを確認(組み込みfetchを使用、追加パッケージ不要) + +## ファイル構成 + +| ファイル | 役割 | +|---|---| +| `config.json` | 接続情報(BaseUrl/SiteId/ApiKey)※Git管理・共有厳禁 | +| `config.example.json` | 上記のテンプレート | +| `get-site-config.js` | サイト構成取得(`getsite`) → `configs/site-{SiteId}_latest.json` に保存し、続けて `extract-site-config.js` を自動実行。`SiteId` をカンマ区切りにすると全サイトを1回の実行で順次取得 | +| `extract-site-config.js` | `latest.json` から Scripts/Styles/ServerScripts/Processes/ガイドHTML を `configs/site-{SiteId}_{サイト名}/` に個別ファイル分割(`get-site-config.js` から自動呼び出し、または単独実行も可) | +| `build-desired-config.js` | 分割ファイルを再構成し `desired_config.json` を生成(**送信は行わない**) | +| `apply-site-config.js` | 希望構成JSONとの差分表示・送信内容確認(**送信は行わない**) | +| `desired_config.json` | Claude、または `build-desired-config.js` が生成する「希望構成」 | +| `configs/site-{SiteId}_latest.json` | 直近取得した現状構成(Claudeへのアップロード対象) | +| `configs/site-{SiteId}_spec.md` | SiteSettingsを読み解いたMarkdown仕様書 | +| `configs/site-{SiteId}_{サイト名}/manifest.json` | 分割ファイルの一覧(Id/Title/ファイル名の対応表) | +| `configs/site-{SiteId}_{サイト名}/scripts/*.js` | クライアントスクリプト本体(1ファイル=1スクリプト) | +| `configs/site-{SiteId}_{サイト名}/styles/*.css` | スタイル本体 | +| `configs/site-{SiteId}_{サイト名}/serverscripts/*.js` | サーバースクリプト本体 | +| `configs/site-{SiteId}_{サイト名}/html/*.html` | ガイドHTML(GridGuide等。値が空でないもののみ) | +| `configs/site-{SiteId}_{サイト名}/processes.json` | プロセス設定(`SiteSettings.Processes` の定義ファイル) | +| `md-to-pdf.js` | Markdownファイルを同名の`.pdf`に変換(追加npmパッケージ非依存、OS標準のEdge/Chromeのヘッドレス印刷を利用) | +| `docs/site-{SiteId}_overview.md` | 業務担当者向けの概要書(ColumnName等の内部コードを含まない機能説明)。`configs/`と異なりGit管理対象 | +| `docs/site-{SiteId}_overview.pdf` | 上記概要書のPDF版 | + +## Scripts/Styles/ServerScripts/Processes の反映について + +- `build-desired-config.js` が生成する `desired_config.json` は `Mode:"partial"`(`updatesitesettings`)です。 + Scripts / Styles / ServerScripts / Processes の4項目のみを対象とし、それ以外のサイト設定(GridColumns等)には触れません。 +- ガイドHTML(`html/*.html`)はSiteSettingsではなく**サイトデータ直下**の項目のため、`updatesitesettings` では反映できません。 + 反映したい場合は `Mode:"full"`(`updatesite`)用の別の `desired_config.json` を用意し、`Title`/`ReferenceType` 等の必須項目とあわせて指定してください。 +- 抽出(`extract-site-config.js`)はその時点のスナップショットです。抽出後に元サイトが他の人・画面から変更されている可能性がある場合は、 + 反映前に `node get-site-config.js` → `node extract-site-config.js` を再実行し、最新状態を取り直してから編集してください。 + +## 参照した公式API仕様 + +- サイト情報取得: `POST {BaseUrl}/api/items/{SiteId}/getsite` + https://pleasanter.org/ja/manual/api-site-get + (※ `/api/items/{SiteId}/get` はアイテム一覧取得APIであり別物) +- サイト全体更新: `POST {BaseUrl}/api/items/{SiteId}/updatesite` + https://pleasanter.org/manual/api-site-update +- サイト設定部分更新: `POST {BaseUrl}/api/items/{SiteId}/updatesitesettings` + https://pleasanter.org/ja/manual/api-update-sitesettings + +## 安全設計について + +- `apply-site-config.js` は **常にドライラン** です。デフォルトでは実際のAPI呼び出しコード自体に到達しないようガード(`ENABLE_ACTUAL_SEND = false` 固定)しています。 +- 反映する際は、表示されたcurlコマンドをご自身で実行するか、内容を確認の上でスクリプトを改修してください(Claudeが安全策なしに自動実行することはありません)。 +- APIキーは `config.json` にのみ保持し、Claudeにアップロードする `latest.json` には含まれません(get時のレスポンスにAPIキーは含まれないため)。念のためアップロード前に目視確認することをおすすめします。 + +## 注意事項 + +- プリザンターのバージョンにより `get` レスポンスの構造(`Response.Site.SiteSettings` の位置など)が異なる場合があります。初回実行時にコンソールへ出力されるキー構造を確認してください。 +- サイト設定の更新系APIは **テナント管理者権限のAPIキー** が必要です(一般ユーザーのAPIキーでは403等になります)。 diff --git a/ClaudePleasanter/助っ人クラウド/apply-site-config.js b/ClaudePleasanter/助っ人クラウド/apply-site-config.js new file mode 100644 index 00000000..9c8cd91d --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/apply-site-config.js @@ -0,0 +1,156 @@ +/** + * apply-site-config.js + * ------------------------------------------------------------ + * ★★★ このスクリプトは常にドライランです。実際のAPI送信は一切行いません。★★★ + * + * Claudeが生成した desired_config.json(希望構成)を読み込み、 + * 1. 現状構成(configs/site-{SiteId}_latest.json)との差分表示 + * 2. 実際に送信されるはずの HTTPリクエスト(URL / Body)の表示 + * 3. そのまま使えるcurlコマンドの出力 + * のみを行います。実行(fetch送信)は行いません。 + * + * 内容を確認した上で、問題なければ、 + * - 表示されたcurlコマンドを手動で実行する + * - または、このファイル末尾の "実送信ブロック" のコメントを + * 自分の判断で外して実行する + * のいずれかで反映してください。 + * + * desired_config.json の形式: + * { + * "Mode": "partial", // "partial" = updatesitesettings(部分更新) / "full" = updatesite(全体更新) + * "SiteId": 12345, // 省略時は config.json の値を使用 + * "SiteSettings": { ... }, // 変更したい項目のみ(partialの場合)/ 全体(fullの場合) + * "Title": "...", // full更新時のみ必要な場合あり + * "ReferenceType": "...", + * "ParentId": ..., + * "InheritPermission": ... + * } + * + * 参照した公式マニュアル: + * - サイト更新(全体): https://pleasanter.org/manual/api-site-update + * URL: POST {BaseUrl}/api/items/{SiteId}/updatesite + * - サイト設定の部分更新: https://pleasanter.org/ja/manual/api-update-sitesettings + * URL: POST {BaseUrl}/api/items/{SiteId}/updatesitesettings + * ------------------------------------------------------------ + */ + +const fs = require("fs"); +const path = require("path"); + +function loadJson(p, required = true) { + if (!fs.existsSync(p)) { + if (required) { + console.error(`[エラー] ファイルが見つかりません: ${p}`); + process.exit(1); + } + return null; + } + return JSON.parse(fs.readFileSync(p, "utf-8")); +} + +// ざっくりした差分表示(キー単位・トップ1階層+SiteSettings1階層まで) +// partialモード(updatesitesettings)は desired 側にあるキーだけがサーバーに送られ、 +// 記載のないキーは現状のまま変わらない。そのため比較対象は afterKeys のみに限定する +// (beforeにしかないキーを「削除される」ように見せるのは誤り)。 +// fullモード(updatesite)はSiteSettings全体を丸ごと置き換えるため、beforeKeysも含めて比較する。 +function diffTopLevel(before, after, label, mode = "partial") { + if (!before) { + console.log(` (現状データなし。get-site-config.jsで先に取得しておくと差分表示できます)`); + return; + } + const afterKeys = new Set(Object.keys(after || {})); + const allKeys = mode === "full" ? new Set([...Object.keys(before || {}), ...afterKeys]) : afterKeys; + + let changed = false; + for (const key of allKeys) { + const b = JSON.stringify(before?.[key]); + const a = JSON.stringify(after?.[key]); + if (b !== a) { + changed = true; + console.log(` [変更] ${label}.${key}`); + console.log(` - 現状: ${truncate(b)}`); + console.log(` + 希望: ${truncate(a)}`); + } + } + if (!changed) console.log(" 差分なし"); +} + +function truncate(str, max = 200) { + if (str === undefined) return "(undefined)"; + return str.length > max ? str.slice(0, max) + "...(略)" : str; +} + +function maskApiKey(key) { + if (!key) return key; + return key.length > 8 ? key.slice(0, 4) + "****" + key.slice(-4) : "****"; +} + +async function main() { + const baseDir = __dirname; + const configPath = path.join(baseDir, "config.json"); + const desiredPath = process.argv[2] || path.join(baseDir, "desired_config.json"); + + const config = loadJson(configPath); + const desired = loadJson(desiredPath); + + const siteId = desired.SiteId || config.SiteId; + const mode = desired.Mode || "partial"; + const endpoint = mode === "full" ? "updatesite" : "updatesitesettings"; + const url = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${siteId}/${endpoint}`; + + const latestConfigPath = path.join(baseDir, "configs", `site-${siteId}_latest.json`); + const current = loadJson(latestConfigPath, false); + const currentSiteSettings = + current?.Response?.Data?.SiteSettings || + current?.Response?.Site?.SiteSettings || + current?.Response?.SiteSettings || + null; + + console.log("========================================"); + console.log(" ドライラン結果(実際の送信は行いません)"); + console.log("========================================"); + console.log(`Mode : ${mode} (${mode === "full" ? "全体更新" : "部分更新"})`); + console.log(`URL : ${url}`); + console.log(`SiteId : ${siteId}`); + console.log(""); + console.log("--- SiteSettings 差分(現状 → 希望) ---"); + diffTopLevel(currentSiteSettings, desired.SiteSettings, "SiteSettings", mode); + console.log(""); + + const body = { + ApiVersion: config.ApiVersion || "1.1", + ApiKey: config.ApiKey, + ...desired, + }; + delete body.Mode; // Mode はエンドポイント選択にのみ使用し、送信Bodyには含めない + + const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) }; + console.log("--- 送信されるはずのBody(APIキーはマスク表示) ---"); + console.log(JSON.stringify(maskedBody, null, 2)); + console.log(""); + + console.log("--- 手動実行用 curl コマンド(内容確認の上、必要ならご自身で実行してください) ---"); + console.log( + `curl -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify(body)}'` + ); + console.log(""); + console.log("[注意] このスクリプトはAPIへの送信を一切行っていません(ドライラン専用)。"); + console.log("内容に問題がなければ、上記curlコマンドを手動実行するか、"); + console.log("スクリプト内の「実送信ブロック」を有効化する改修をご自身の判断で行ってください。"); + + // ------------------------------------------------------------ + // 実送信ブロック(デフォルトでは絶対に到達しないようにガードしています) + // 意図的に反映を自動化したい場合のみ、下のガードを外して使ってください。 + // ------------------------------------------------------------ + const ENABLE_ACTUAL_SEND = false; // ← 安全のため常にfalse固定。変更は自己責任で。 + if (ENABLE_ACTUAL_SEND) { + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + console.log(await res.text()); + } +} + +main(); diff --git a/ClaudePleasanter/助っ人クラウド/build-desired-config.js b/ClaudePleasanter/助っ人クラウド/build-desired-config.js new file mode 100644 index 00000000..cca66dea --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/build-desired-config.js @@ -0,0 +1,139 @@ +/** + * build-desired-config.js + * ------------------------------------------------------------ + * extract-site-config.js で分割した configs/site-{SiteId}/ 配下のファイル + * (scripts/*.js, styles/*.css, serverscripts/*.js, processes.json)を + * manifest.json を手がかりに再構成し、apply-site-config.js が読み込む + * desired_config.json を生成します。 + * + * ★★★ このスクリプトもAPIへは一切送信しません(ファイル生成のみ)。★★★ + * 反映は既存の運用どおり、 + * 1. node apply-site-config.js でドライラン確認(差分・送信Body・curl表示) + * 2. 内容を確認し、問題なければ表示されたcurlコマンドを手動実行 + * の流れに乗せてください。 + * + * 使い方: + * node build-desired-config.js site-214812 + * (フォルダ名は "site-{SiteId}_{サイト名}" 形式だが、"site-{SiteId}" だけの + * 前方一致指定でも configs/ 配下から自動的に該当フォルダを探して解決する) + * node build-desired-config.js ./configs/site-214812_物件基本情報 + * + * 出力: desired_config.json(このスクリプトと同じ階層。既存ファイルは上書き) + * + * 対応範囲: + * Scripts / Styles / ServerScripts / Processes + * → Mode:"partial"(updatesitesettings)で反映可能なため、そのまま組み込みます。 + * Html(GridGuide等のガイド) + * → SiteSettingsではなくサイトデータ直下の項目のため、 + * 部分更新(updatesitesettings)には含められません。 + * 反映したい場合は Mode:"full"(updatesite)用の別JSONを用意し、 + * Title/ReferenceType等の必須項目とあわせて指定してください。 + * このスクリプトはhtml/配下に抽出物がある場合、警告のみ表示します。 + * ------------------------------------------------------------ + */ + +const fs = require("fs"); +const path = require("path"); + +function readJson(p) { + return JSON.parse(fs.readFileSync(p, "utf-8")); +} + +// フォルダ名は "site-{SiteId}_{サイト名}" 形式。"site-{SiteId}" のみの指定でも +// configs/ 配下から前方一致で該当フォルダを探して解決する。 +// 新形式(サイト名付き)を優先し、裸の "site-{SiteId}" 完全一致(リネーム前の古い抽出結果の可能性)は +// 他に候補が無い場合のみ後方互換フォールバックとして使う。 +function resolveSiteDir(baseDir, arg) { + const configsDir = path.join(baseDir, "configs"); + const direct = path.isAbsolute(arg) ? arg : path.join(configsDir, path.basename(arg)); + const baseName = path.basename(arg); + + const candidates = fs.existsSync(configsDir) + ? fs.readdirSync(configsDir).filter((name) => { + const full = path.join(configsDir, name); + return ( + fs.statSync(full).isDirectory() && (name === baseName || name.startsWith(`${baseName}_`)) + ); + }) + : []; + + const suffixed = candidates.filter((name) => name !== baseName); + if (suffixed.length === 1) return path.join(configsDir, suffixed[0]); + if (suffixed.length > 1) { + console.error(`[エラー] "${baseName}" に一致するフォルダが複数見つかりました: ${suffixed.join(", ")}`); + console.error("フォルダ名をフルで指定してください。"); + process.exit(1); + } + + if (candidates.includes(baseName)) return path.join(configsDir, baseName); + if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) return direct; + + console.error(`[エラー] フォルダが見つかりません: ${direct}`); + console.error("先に node extract-site-config.js(またはnode get-site-config.js)を実行してください。"); + process.exit(1); +} + +function rebuildList(manifestEntries, dir, bodyKey = "Body") { + return (manifestEntries || []).map((entry) => { + const { File, ...meta } = entry; + const filePath = path.join(dir, File); + if (!fs.existsSync(filePath)) { + console.error(`[エラー] manifest.jsonに記載のファイルが見つかりません: ${filePath}`); + process.exit(1); + } + const body = fs.readFileSync(filePath, "utf-8"); + return { ...meta, [bodyKey]: body }; + }); +} + +function main() { + const baseDir = __dirname; + const arg = process.argv[2]; + if (!arg) { + console.error("[エラー] 対象フォルダを指定してください(例: node build-desired-config.js site-214812)"); + process.exit(1); + } + const siteDir = resolveSiteDir(baseDir, arg); + + const manifestPath = path.join(siteDir, "manifest.json"); + if (!fs.existsSync(manifestPath)) { + console.error(`[エラー] manifest.jsonが見つかりません: ${manifestPath}`); + console.error("先に node extract-site-config.js を実行してください。"); + process.exit(1); + } + const manifest = readJson(manifestPath); + + const siteSettings = {}; + + siteSettings.Scripts = rebuildList(manifest.Scripts, path.join(siteDir, "scripts")); + siteSettings.Styles = rebuildList(manifest.Styles, path.join(siteDir, "styles")); + siteSettings.ServerScripts = rebuildList(manifest.ServerScripts, path.join(siteDir, "serverscripts")); + + const processesPath = path.join(siteDir, manifest.ProcessesFile || "processes.json"); + siteSettings.Processes = fs.existsSync(processesPath) ? readJson(processesPath) : []; + + const desired = { + Mode: "partial", + SiteId: manifest.SiteId, + SiteSettings: siteSettings, + }; + + const outPath = path.join(baseDir, "desired_config.json"); + fs.writeFileSync(outPath, JSON.stringify(desired, null, 2), "utf-8"); + + console.log(`[OK] 生成しました: ${outPath}`); + console.log(` Scripts : ${siteSettings.Scripts.length}件`); + console.log(` Styles : ${siteSettings.Styles.length}件`); + console.log(` ServerScripts : ${siteSettings.ServerScripts.length}件`); + console.log(` Processes : ${siteSettings.Processes.length}件`); + + if (manifest.Html && manifest.Html.length > 0) { + console.log("\n[注意] html/ 配下のガイドHTMLは、この desired_config.json には含まれていません。"); + console.log(" GridGuide等はSiteSettingsではなくサイト直下の項目のため、部分更新(updatesitesettings)では送れません。"); + console.log(' 反映するには Mode:"full"(updatesite)用の別JSONを用意し、Title等の必須項目とあわせて指定してください。'); + } + + console.log("\n次のステップ: node apply-site-config.js でドライラン確認 → 内容確認の上、表示されたcurlコマンドを手動実行してください。"); +} + +main(); diff --git a/ClaudePleasanter/助っ人クラウド/config.example.json b/ClaudePleasanter/助っ人クラウド/config.example.json new file mode 100644 index 00000000..c449c351 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/config.example.json @@ -0,0 +1,6 @@ +{ + "BaseUrl": "http://your-pleasanter-server/", + "SiteId": "12345,23456", + "ApiKey": "ここに発行したAPIキーを貼り付け", + "ApiVersion": "1.1" +} diff --git a/ClaudePleasanter/助っ人クラウド/extract-site-config.js b/ClaudePleasanter/助っ人クラウド/extract-site-config.js new file mode 100644 index 00000000..1781382d --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/extract-site-config.js @@ -0,0 +1,195 @@ +/** + * extract-site-config.js + * ------------------------------------------------------------ + * 取得済みのサイト構成 (configs/site-{SiteId}_latest.json) から、 + * Scripts / Styles / ServerScripts / Processes / ガイドHTML を + * 個別ファイルとして configs/site-{SiteId}/ 配下に書き出します。 + * + * 使い方: + * node extract-site-config.js + * node extract-site-config.js ./configs/site-214812_latest.json + * + * 出力: + * configs/site-{SiteId}/ + * manifest.json ← 抽出内容の一覧(build-desired-config.jsでの再構成に使用) + * scripts/{Id}_{Title}.js + * styles/{Id}_{Title}.css + * serverscripts/{Id}_{Title}.js + * html/{GuideName}.html ← GridGuide等、ガイドHTML(値が空でないもののみ) + * processes.json ← プロセス設定(SiteSettings.Processes をそのまま保存) + * + * 注意: + * ガイドHTML(GridGuide等)はSiteSettingsではなく、サイトデータ直下の項目です。 + * そのため反映時はサイト設定の部分更新(updatesitesettings)ではなく、 + * サイト全体更新(updatesite / Mode:"full")が必要になります。 + * Scripts/Styles/ServerScripts/Processesは部分更新(Mode:"partial")で反映できます。 + * 詳しくは build-desired-config.js の実行結果メッセージを参照してください。 + * ------------------------------------------------------------ + */ + +const fs = require("fs"); +const path = require("path"); + +function sanitize(name) { + return ( + String(name || "untitled") + .replace(/[\\/:*?"<>|]/g, "_") + .trim() + .slice(0, 60) || "untitled" + ); +} + +function ensureDir(dir) { + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); +} + +function extractList(items, outDir, ext, bodyKey = "Body") { + const manifestEntries = []; + if (!items || items.length === 0) return manifestEntries; + ensureDir(outDir); + for (const item of items) { + const fileName = `${item.Id}_${sanitize(item.Title || item.Name)}${ext}`; + fs.writeFileSync(path.join(outDir, fileName), item[bodyKey] || "", "utf-8"); + const { [bodyKey]: _omit, ...meta } = item; + manifestEntries.push({ ...meta, File: fileName }); + } + return manifestEntries; +} + +// config.json の SiteId(数値 / カンマ区切り文字列 / 配列)を +// 個々のSiteId文字列の配列に正規化する(get-site-config.jsと同仕様)。 +function parseSiteIds(rawSiteId) { + const list = Array.isArray(rawSiteId) ? rawSiteId : String(rawSiteId).split(","); + return list + .map((id) => String(id).trim()) + .filter((id) => id.length > 0); +} + +const GUIDE_FIELDS = [ + "GridGuide", + "EditorGuide", + "CalendarGuide", + "CrosstabGuide", + "GanttGuide", + "BurnDownGuide", + "TimeSeriesGuide", + "AnalyGuide", + "KambanGuide", + "ImageLibGuide", +]; + +// get-site-config.js からも呼び出せるよう関数化。 +// CLI実行時(直接 `node extract-site-config.js`)は末尾のブロックが引数解決を行う。 +function extractSiteConfig(latestPath) { + const baseDir = __dirname; + + if (!fs.existsSync(latestPath)) { + console.error(`[エラー] ファイルが見つかりません: ${latestPath}`); + console.error("先に node get-site-config.js を実行してください。"); + process.exit(1); + } + + const json = JSON.parse(fs.readFileSync(latestPath, "utf-8")); + const data = json?.Response?.Data; + if (!data) { + console.error("[エラー] Response.Data が見つかりません。getsiteのレスポンス形式を確認してください。"); + process.exit(1); + } + + const siteId = data.SiteId; + const settings = data.SiteSettings || {}; + const dirName = `site-${siteId}_${sanitize(data.Title)}`; + const outRoot = path.join(baseDir, "configs", dirName); + ensureDir(outRoot); + + const manifest = { + SiteId: siteId, + Title: data.Title, + ExtractedAt: new Date().toISOString(), + SourceFile: path.relative(baseDir, latestPath).replace(/\\/g, "/"), + }; + + manifest.Scripts = extractList(settings.Scripts, path.join(outRoot, "scripts"), ".js"); + manifest.Styles = extractList(settings.Styles, path.join(outRoot, "styles"), ".css"); + manifest.ServerScripts = extractList(settings.ServerScripts, path.join(outRoot, "serverscripts"), ".js"); + + // ガイドHTML(サイトデータ直下。SiteSettingsではない点に注意) + const htmlDir = path.join(outRoot, "html"); + const htmlEntries = []; + for (const field of GUIDE_FIELDS) { + const value = data[field]; + if (value) { + ensureDir(htmlDir); + const fileName = `${field}.html`; + fs.writeFileSync(path.join(htmlDir, fileName), value, "utf-8"); + htmlEntries.push({ Field: field, File: fileName, TopLevel: true }); + } + } + manifest.Html = htmlEntries; + + // プロセス設定 … 個別分割せず1つの定義ファイルとして保存 + fs.writeFileSync( + path.join(outRoot, "processes.json"), + JSON.stringify(settings.Processes || [], null, 2), + "utf-8" + ); + manifest.ProcessesFile = "processes.json"; + manifest.ProcessesCount = (settings.Processes || []).length; + + fs.writeFileSync(path.join(outRoot, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8"); + + console.log(`[OK] 抽出完了: ${outRoot}`); + console.log(` Scripts : ${manifest.Scripts.length}件`); + console.log(` Styles : ${manifest.Styles.length}件`); + console.log(` ServerScripts : ${manifest.ServerScripts.length}件`); + console.log(` Html(ガイド) : ${manifest.Html.length}件`); + console.log(` Processes : ${manifest.ProcessesCount}件 → processes.json`); + console.log("\n次のステップ: 各ファイルを直接編集するか、Claudeに修正内容を指示してください。"); + console.log(`修正後は次のコマンドで反映用JSONを作成できます: node build-desired-config.js ${dirName}`); + + return manifest; +} + +// CLIとして直接実行された場合のみ、引数からlatestPathを解決して実行する +if (require.main === module) { + const baseDir = __dirname; + const latestPathArg = process.argv[2]; + + if (latestPathArg) { + extractSiteConfig(latestPathArg); + } else { + const configPath = path.join(baseDir, "config.json"); + if (!fs.existsSync(configPath)) { + console.error("[エラー] config.jsonが見つかりません。抽出対象のJSONパスを引数で指定してください。"); + process.exit(1); + } + const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); + const siteIds = parseSiteIds(config.SiteId); + if (siteIds.length === 0) { + console.error("[エラー] config.json の SiteId から有効なサイトIDを取得できませんでした。"); + process.exit(1); + } + + const missing = []; + for (const siteId of siteIds) { + const latestPath = path.join(baseDir, "configs", `site-${siteId}_latest.json`); + if (!fs.existsSync(latestPath)) { + console.error(`[警告] ファイルが見つかりません(スキップ): ${latestPath}`); + missing.push(siteId); + continue; + } + if (siteIds.length > 1) { + console.log(`\n==== SiteId: ${siteId} ====`); + } + extractSiteConfig(latestPath); + } + + if (missing.length > 0) { + console.error(`\n[エラー] 未取得のSiteIdがあります: ${missing.join(", ")}`); + console.error("先に node get-site-config.js を実行してください。"); + process.exit(1); + } + } +} + +module.exports = { extractSiteConfig }; diff --git a/ClaudePleasanter/助っ人クラウド/get-site-config.js b/ClaudePleasanter/助っ人クラウド/get-site-config.js new file mode 100644 index 00000000..095d1ee4 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/get-site-config.js @@ -0,0 +1,166 @@ +/** + * get-site-config.js + * ------------------------------------------------------------ + * プリザンターの指定サイトの構成情報(SiteSettings等)を取得し、 + * ./configs/ 配下にタイムスタンプ付きJSONとして保存します。 + * + * 使い方: + * node get-site-config.js + * node get-site-config.js ./config.json ← 設定ファイルを明示指定する場合 + * + * 事前準備: + * 1. config.example.json を config.json にコピーし、 + * BaseUrl / SiteId / ApiKey を実環境の値に書き換えてください。 + * SiteId はカンマ区切りで複数指定可能です(例: "480111,438166,96235")。 + * 1件のみの場合は従来通り数値(例: 480111)でも構いません。 + * 2. Node.js 18以降(組み込みfetchを使用) + * + * 参照した公式マニュアル: + * - 開発者向け機能:API:サイト操作:サイト更新 + * https://pleasanter.org/manual/api-site-update + * - 開発者向け機能:API:サイト操作:サイト設定の更新(部分追加/更新/削除) + * https://pleasanter.org/ja/manual/api-update-sitesettings + * + * 注意: + * プリザンターのバージョンにより get のレスポンス構造 + * (SiteSettingsが Response.Site 配下にあるか等)が異なる場合があります。 + * 初回実行時はコンソールに出力されるレスポンスのトップレベルキーを確認し、 + * 想定通りの構造か確認してください。 + * ------------------------------------------------------------ + */ + +const fs = require("fs"); +const path = require("path"); +const { extractSiteConfig } = require("./extract-site-config"); + +// config.json の SiteId(数値 / カンマ区切り文字列 / 配列)を +// 個々のSiteId文字列の配列に正規化する。 +function parseSiteIds(rawSiteId) { + const list = Array.isArray(rawSiteId) ? rawSiteId : String(rawSiteId).split(","); + return list + .map((id) => String(id).trim()) + .filter((id) => id.length > 0); +} + +// 1サイト分の取得・保存・抽出処理 +async function fetchAndSaveSite(siteId, { BaseUrl, ApiKey, ApiVersion }) { + const url = `${BaseUrl.replace(/\/+$/, "")}/api/items/${siteId}/getsite`; + const body = { + ApiVersion: ApiVersion || "1.1", + ApiKey: ApiKey, + }; + + console.log(`[INFO] 取得先: ${url}`); + console.log(`[INFO] SiteId: ${siteId}`); + + let response; + try { + response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + } catch (err) { + console.error("[エラー] リクエスト送信に失敗しました:", err.message); + console.error("BaseUrlの疎通・ネットワーク設定を確認してください。"); + return false; + } + + const text = await response.text(); + + if (!response.ok) { + console.error(`[エラー] HTTP ${response.status} ${response.statusText}`); + console.error(text); + return false; + } + + let json; + try { + json = JSON.parse(text); + } catch (err) { + console.error("[エラー] レスポンスがJSONとして解析できませんでした。"); + console.error(text); + return false; + } + + // レスポンス構造の確認用ログ(初回はここを見て構造を把握してください) + console.log("[INFO] レスポンス トップレベルキー:", Object.keys(json)); + if (json.Response) { + console.log("[INFO] Response配下のキー:", Object.keys(json.Response)); + } + + // 保存 + const outDir = path.join(__dirname, "configs"); + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + + const timestamp = new Date() + .toISOString() + .replace(/[:.]/g, "-"); + const outFile = path.join(outDir, `site-${siteId}_${timestamp}.json`); + + fs.writeFileSync(outFile, JSON.stringify(json, null, 2), "utf-8"); + + // 常に「最新」ファイルも別名で保持(Claudeへのアップロード用に固定名があると便利) + const latestFile = path.join(outDir, `site-${siteId}_latest.json`); + fs.writeFileSync(latestFile, JSON.stringify(json, null, 2), "utf-8"); + + console.log(`[OK] 保存しました: ${outFile}`); + console.log(`[OK] 最新版としても保存しました: ${latestFile}`); + + // 取得と同時に Scripts/Styles/ServerScripts/Processes/ガイドHTML も抽出する + console.log("\n[INFO] 続けて設定の個別ファイル抽出を実行します..."); + extractSiteConfig(latestFile); + + return true; +} + +async function main() { + const configPath = process.argv[2] || path.join(__dirname, "config.json"); + + if (!fs.existsSync(configPath)) { + console.error(`[エラー] 設定ファイルが見つかりません: ${configPath}`); + console.error("config.example.json をコピーして config.json を作成してください。"); + process.exit(1); + } + + const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); + const { BaseUrl, SiteId, ApiKey, ApiVersion } = config; + + if (!BaseUrl || !SiteId || !ApiKey) { + console.error("[エラー] config.json に BaseUrl / SiteId / ApiKey を設定してください。"); + process.exit(1); + } + + const siteIds = parseSiteIds(SiteId); + if (siteIds.length === 0) { + console.error("[エラー] config.json の SiteId から有効なサイトIDを取得できませんでした。"); + process.exit(1); + } + + const multiple = siteIds.length > 1; + const results = []; + + for (const [index, siteId] of siteIds.entries()) { + if (multiple) { + console.log(`\n==== [${index + 1}/${siteIds.length}] SiteId: ${siteId} ====`); + } + const ok = await fetchAndSaveSite(siteId, { BaseUrl, ApiKey, ApiVersion }); + results.push({ siteId, ok }); + } + + if (multiple) { + console.log("\n==== 取得結果まとめ ===="); + for (const { siteId, ok } of results) { + console.log(` SiteId ${siteId}: ${ok ? "OK" : "失敗"}`); + } + } + + console.log("\n次のステップ: latest.jsonをClaudeのチャットにアップロードして仕様書化を依頼するか、"); + console.log("configs/site-{SiteId}/ 配下のファイルを直接編集・指示して修正してください。"); + + if (results.some((r) => !r.ok)) { + process.exit(1); + } +} + +main(); diff --git a/ClaudePleasanter/助っ人クラウド/md-to-pdf.js b/ClaudePleasanter/助っ人クラウド/md-to-pdf.js new file mode 100644 index 00000000..4e5c82e1 --- /dev/null +++ b/ClaudePleasanter/助っ人クラウド/md-to-pdf.js @@ -0,0 +1,233 @@ +/** + * md-to-pdf.js + * ------------------------------------------------------------ + * Markdownファイル(見出し/表/太字/リンク/箇条書き/水平線程度の書式)を + * 同名の .pdf として書き出します。外部npmパッケージ非依存(プロジェクトに + * package.jsonが無いことに合わせ、Node標準モジュールのみで完結させています)。 + * PDF化にはOS標準のMicrosoft Edge(無ければGoogle Chrome)のヘッドレス印刷機能を使用します。 + * + * 使い方: + * node md-to-pdf.js ./docs/site-475384_overview.md + * node md-to-pdf.js ./docs/*.md (シェルのグロブ展開に依存。複数ファイル可) + * + * 出力: + * 入力と同じフォルダに {同名}.pdf を生成(中間HTMLは生成後に削除) + * + * 注意: + * フォルダ名に # を含むパス(例: #GitHub)は file:// URL上でフラグメント区切りと + * 誤認識されるため、url.pathToFileURL() で必ずエンコードしてからブラウザに渡すこと。 + * ------------------------------------------------------------ + */ + +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const url = require("url"); +const { execFileSync } = require("child_process"); + +const CANDIDATE_BROWSERS = [ + "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe", + "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", + "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", +]; + +function findBrowser() { + for (const p of CANDIDATE_BROWSERS) { + if (fs.existsSync(p)) return p; + } + console.error("[エラー] Microsoft EdgeまたはGoogle Chromeが見つかりませんでした。"); + console.error("インストール済みのブラウザパスをCANDIDATE_BROWSERSに追加してください。"); + process.exit(1); +} + +// --- 最小限のMarkdown→HTML変換(見出し/表/太字/リンク/箇条書き/水平線/段落のみ対応) --- +function escapeHtml(s) { + return s + .replace(/&/g, "&") + .replace(//g, ">"); +} + +function renderInline(text) { + let t = escapeHtml(text); + t = t.replace(/\*\*(.+?)\*\*/g, "$1"); + t = t.replace(/`([^`]+)`/g, "$1"); + t = t.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + return t; +} + +function isTableSeparator(line) { + return /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(line) && line.includes("-"); +} + +function splitTableRow(line) { + let cells = line.trim(); + if (cells.startsWith("|")) cells = cells.slice(1); + if (cells.endsWith("|")) cells = cells.slice(0, -1); + return cells.split("|").map((c) => c.trim()); +} + +function markdownToHtml(mdText) { + const lines = mdText.split(/\r?\n/); + const html = []; + let i = 0; + let inList = false; + + function closeList() { + if (inList) { + html.push(""); + inList = false; + } + } + + while (i < lines.length) { + const line = lines[i]; + + if (/^\s*$/.test(line)) { + closeList(); + i++; + continue; + } + + const heading = line.match(/^(#{1,6})\s+(.*)$/); + if (heading) { + closeList(); + const level = heading[1].length; + html.push(`${renderInline(heading[2])}`); + i++; + continue; + } + + if (/^\s*---+\s*$/.test(line)) { + closeList(); + html.push("
"); + i++; + continue; + } + + // テーブル: 次行がセパレータ行であること + if (line.includes("|") && i + 1 < lines.length && isTableSeparator(lines[i + 1])) { + closeList(); + const headerCells = splitTableRow(line); + html.push("" + headerCells.map((c) => ``).join("") + ""); + i += 2; + while (i < lines.length && lines[i].includes("|") && !/^\s*$/.test(lines[i])) { + const rowCells = splitTableRow(lines[i]); + html.push("" + rowCells.map((c) => ``).join("") + ""); + i++; + } + html.push("
${renderInline(c)}
${renderInline(c)}
"); + continue; + } + + const listItem = line.match(/^\s*-\s+(.*)$/); + if (listItem) { + if (!inList) { + html.push(""); + inList = false; + } + } + + while (i < lines.length) { + const line = lines[i]; + + if (/^\s*$/.test(line)) { + closeList(); + i++; + continue; + } + + const heading = line.match(/^(#{1,6})\s+(.*)$/); + if (heading) { + closeList(); + const level = heading[1].length; + html.push(`${renderInline(heading[2])}`); + i++; + continue; + } + + if (/^\s*---+\s*$/.test(line)) { + closeList(); + html.push("
"); + i++; + continue; + } + + // テーブル: 次行がセパレータ行であること + if (line.includes("|") && i + 1 < lines.length && isTableSeparator(lines[i + 1])) { + closeList(); + const headerCells = splitTableRow(line); + html.push("" + headerCells.map((c) => ``).join("") + ""); + i += 2; + while (i < lines.length && lines[i].includes("|") && !/^\s*$/.test(lines[i])) { + const rowCells = splitTableRow(lines[i]); + html.push("" + rowCells.map((c) => ``).join("") + ""); + i++; + } + html.push("
${renderInline(c)}
${renderInline(c)}
"); + continue; + } + + const listItem = line.match(/^\s*-\s+(.*)$/); + if (listItem) { + if (!inList) { + html.push("