Next.js 15 App Router で Tailwind CSS V4 を使用してダークモードを追加する方法

概要 Next.js 15 App Router で Tailwind CSS V4 を使用してダークモードを追加する方法です。 以下の記事が参考になりました。 https://sujalvanjare.vercel.app/blog/dark-mode-nextjs15-tailwind-v4 以下のように、ダークモードとライトモードを切り替えることができました。 上記の記事について、ChatGPTによる日本語記事です。 Next.js 15 App RouterプロジェクトでTailwind CSS V4を使用してダークモードとライトモードを実装する方法を学びましょう。このステップバイステップガイドでは、シームレスなテーマスイッチャーを実現するためのTailwind V4とNext.js 15の最新の変更点をカバーしています! 既にNext.jsプロジェクトをお持ちの場合は、ステップ1に進んでください。 ステップ1:新しいNext.jsプロジェクトを作成する App RouterとTailwind CSS v4を搭載したNext.js 15をインストールするには、次の手順に従ってください: 任意の場所にプロジェクトフォルダを作成します。 VS Codeでフォルダを開きます。 ターミナルを開き、次のコマンドを実行します: npmユーザーの場合: npx create-next-app@latest . pnpmユーザーの場合: pnpx create-next-app@latest . インストール中に、次のオプションが表示されます: What is your project named? my-app Would you like to use TypeScript? No / Yes Would you like to use ESLint? No / Yes Would you like to use Tailwind CSS? No / Yes Would you like your code inside a `src/` directory? No / Yes Would you like to use App Router? (recommended) No / Yes Would you like to use Turbopack for `next dev`? No / Yes Would you like to customize the import alias (`@/*` by default)? No / Yes What import alias would you like configured? @/* 矢印キーでオプションを選択し、Enterキーを押します。Tailwind CSSとApp Routerを必ず有効にしてください。 ...

2025年4月1日 · 6 分 · Nakamura

Error: Do not use <img>. Use Image from 'next/image' instead.への対応

概要 Error: Do not use . Use Image from ’next/image’ instead.への対応にあたり、以下の記事が参考になりました。 https://stackoverflow.com/questions/68203582/error-do-not-use-img-use-image-from-next-image-instead-next-js-using-ht 上記の記事に基づき、ChatGPTによる回答を以下に掲載します。間違っている点もあるかもしれませんが、参考になりましたら幸いです。 Next.jsのESLintルール変更とフラットコンフィグの設定方法 Next.js 11以降、ESLintの設定がデフォルトで提供されるようになり、@next/next/no-img-element ルールが追加されました。このルールにより、通常の<img>タグの使用が制限され、Next.jsのnext/imageコンポーネントを推奨するようになっています。 しかし、プロジェクトによっては<img>タグを使いたい場合もあるでしょう。そのため、このESLintルールを無効化する方法を紹介します。 1. 従来の設定方法(.eslintrc.js) 以前のNext.jsのESLint設定は、.eslintrc.js に次のように記述することで変更できました。 module.exports = { rules: { "@next/next/no-img-element": "off", }, }; しかし、Next.jsのESLintは「フラットコンフィグ(Flat Configuration)」へ移行しました。そのため、.eslintrc.js ではなく、eslint.config.mjs を使用する必要があります。 2. フラットコンフィグの設定方法(eslint.config.mjs) 現在のNext.jsプロジェクトでESLintのルールを変更するには、eslint.config.mjs ファイルを作成し、以下のように記述します。 import { dirname } from "path"; import { fileURLToPath } from "url"; import { FlatCompat } from "@eslint/eslintrc"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const compat = new FlatCompat({ baseDirectory: __dirname, }); const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), { rules: { "@next/next/no-img-element": "off", }, }, ]; export default eslintConfig; この設定を適用することで、Next.jsのプロジェクトでも通常の<img>タグを使用できるようになります。 3. どの方法を使うべきか? Next.js 11以前のプロジェクト → .eslintrc.js を使用 Next.js 12以降のプロジェクト → eslint.config.mjs を使用 最新のNext.js環境ではフラットコンフィグを利用するのが推奨されているため、新規プロジェクトではeslint.config.mjs を設定するのがベストです。 ...

2025年4月1日 · 1 分 · Nakamura

Omeka Sのモジュールアップデート情報(2025-03-27)

概要 Omeka Sの運用において、モジュールのアップデートが必要になったものを紹介します。 IIIF Server https://omeka.org/s/modules/IiifServer/ 2024年2月にリリースされた3.6.18を使用していましたが、IIIFマニフェストファイル生成時に、サムネイル画像がおかしくなる不具合が確認されました。 2025年3月時点で最新の3.6.24に更新したところ、本不具合が解消しました。なお、このアップデートには、Commonモジュールの更新も必要でしたので、参考になりましたら幸いです。 https://omeka.org/s/modules/Common/ Google Analytics https://github.com/Libnamic/Omeka-S-GoogleAnalytics 2023年頃から使用していましたが、PHPのあるバージョンからwarningが表示されるようになりました。 また本モジュールの更新も、2023年から行われていませんでした。 そこで以下の記事でも紹介したように、Analytics Snippetモジュールを使用したほうがよさそうでした。 以下で更新履歴が確認できますが、こちらは最新のリリースが2025/1になっています。 https://omeka.org/s/modules/AnalyticsSnippet/ まとめ Omeka Sの運用にあたり、参考になりましたら幸いです。

2025年3月27日 · 1 分 · Nakamura

CETEIceanとXPathを使って特定の要素にスクロールする

概要 CETEIceanとXPathを使って特定の要素にスクロールする方法を調べたので備忘録です。 デモ 以下のURLからお試しいただけます。 https://next-ceteicean-router.vercel.app/xpath/ ページにアクセス後、スクロールし、以下のように表示されます。 XPathの取得 上記では、以下の「校異源氏物語テキストDB」のXMLファイルを対象にしています。 https://kouigenjimonogatari.github.io/tei/01.xml そして、以下のXPathを指定しています。 /TEI/text[1]/body[1]/p[1]/seg[267] このXPathの取得にあたっては、Oxygen XML Editorを用いて、対象要素を右クリックして、「Copy XPath」から取得することができました。 スクロールの実装 以下で紹介したアプリをベースにします。 GitHub上のソースコードは以下です。 https://github.com/nakamura196/next-ceteicean-router/blob/main/src/components/xpath/Render.tsx 特に以下の部分で、XPathをCETEIceanによって作成される要素名に変換し、scrollIntoViewによってスクロールしています。 // fetchDataの修正 React.useEffect(() => { const rawXpath = "/TEI/text[1]/body[1]/p[1]/seg[267]"; const xpath = rawXpath .replace(/^\//, "") // 先頭のスラッシュを削除 .replace(/([A-Za-z]+)(?=\/|\[|$)/g, "tei-$1") // tei-プレフィックスを追加 .toLowerCase(); if (teiContentRef.current) { const result = document.evaluate( xpath, teiContentRef.current, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ); const targetElement = result.singleNodeValue as HTMLElement; if (targetElement) { targetElement.scrollIntoView({ behavior: "smooth", block: "center", inline: "center", }); targetElement.style.backgroundColor = "yellow"; } } }, [teiDoc]); まとめ 他にも良い方法があるかもしれませんが、参考になりましたら幸いです。 ...

2025年3月27日 · 1 分 · Nakamura

Mirador 4プラグイン開発:任意の角度で画像を回転するプラグインで、角度の初期値を設定できるようにしました。

概要 任意の角度で画像を回転するMirador 4プラグインで、角度の初期値を設定できるようにしました。 リポジトリは以下です。 https://github.com/nakamura196/mirador-rotation-plugin デモページは以下です。角度および矩形を初期設定とともに、画像を回転させることができます。 https://nakamura196.github.io/mirador-rotation-plugin/ 背景 以下の記事で、本プラグインについて説明しています。 一方、課題として、角度の初期値を与えることができませんでした。 これに対して、以下の記事で紹介したように、Mirador 4の標準機能として、角度の初期値を与えることができるようでした。 合わせて、以下の「mirador-image-tools」プラグインについて、webpackからViteに変更されていたので、この変更を「mirador-rotation-plugin」にも反映することにしました。 https://github.com/ProjectMirador/mirador-image-tools GitHub Pagesでの公開 GitHub Pagesでの公開にあたり、「mirador-image-tools」のvite.config.jsを以下のように変更しています。これで、npm run build:demoにより、GitHub Pagesで公開するためのディレクトリを作成することができるようになりました。 https://github.com/nakamura196/mirador-rotation-plugin/blob/main/vite.config.js import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import fs from 'fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'url'; import { globSync } from 'glob'; import pkg from './package.json'; /** * Vite configuration */ export default defineConfig({ base: process.env.GITHUB_PAGES ? (process.env.BASE_PATH || '/mirador-rotation-plugin/') : '/', ...( process.env.GITHUB_PAGES ? { build: { outDir: 'dist', emptyOutDir: true, rollupOptions: { external: ['__tests__/*', '__mocks__/*'], input: fileURLToPath(new URL('./demo/src/index.html', import.meta.url)), }, sourcemap: true, }, } : { build: { lib: { entry: './src/index.js', fileName: (format) => (format === 'umd' ? 'mirador-rotation.js' : 'mirador-rotation.es.js'), formats: ['es', 'umd'], name: 'MiradorDlPlugin', }, rollupOptions: { external: [...Object.keys(pkg.peerDependencies || {}), '__tests__/*', '__mocks__/*'], output: { assetFileNames: 'mirador-rotation.[ext]', globals: { react: 'React', 'react-dom': 'ReactDOM', }, }, }, sourcemap: true, }, } ), esbuild: { exclude: [], // Matches .js and .jsx in __tests__ and .jsx in src include: [/__tests__\/.*\.(js|jsx)$/, /src\/.*\.jsx?$/], loader: 'jsx', }, optimizeDeps: { esbuildOptions: { plugins: [ { name: 'load-js-files-as-jsx', // TODO: rename all our files to .jsx ... setup(build) { build.onLoad({ filter: /(src|__tests__)\/.*\.js$/ }, async (args) => ({ contents: await fs.readFile(args.path, 'utf8'), loader: 'jsx', })); }, }, ], }, }, plugins: [ react(), // カスタムプラグインを追加してディレクトリ構造を修正 { name: 'fix-output-structure', closeBundle: async () => { if (process.env.GITHUB_PAGES) { const distDir = path.resolve('dist'); const demoSrcDir = path.resolve(distDir, 'demo', 'src'); // demo/src/ディレクトリが存在するか確認 try { const demoSrcStats = await fs.stat(demoSrcDir); if (demoSrcStats.isDirectory()) { console.log('Moving files from demo/src to root directory...'); // demo/src内のファイルリストを取得 const files = await fs.readdir(demoSrcDir); // 各ファイルをルートディレクトリに移動 for (const file of files) { const srcPath = path.join(demoSrcDir, file); const destPath = path.join(distDir, file); const stats = await fs.stat(srcPath); if (stats.isFile()) { await fs.copyFile(srcPath, destPath); console.log(`Copied: ${srcPath} -> ${destPath}`); } } console.log('Files moved successfully.'); // demo/src階層を削除(オプション) // await fs.rm(demoSrcDir, { recursive: true, force: true }); // await fs.rm(path.resolve(distDir, 'demo'), { recursive: true, force: true }); // console.log('Removed original directory structure.'); } } catch (err) { if (err.code !== 'ENOENT') { console.error('Error processing output files:', err); } } } } } ], resolve: { alias: { '@tests/': fileURLToPath(new URL('./__tests__', import.meta.url)), }, }, server: { open: '/demo/src/index.html', port: '4446', }, }); まとめ Mirador 4のプラグイン開発にあたり、参考になりましたら幸いです。 ...

2025年3月26日 · 2 分 · Nakamura

Mirador 4で、初期読み込み時に、画像の回転や範囲指定を行う

概要 Mirador 4で、初期読み込み時に、画像の回転や範囲指定を行う方法を紹介します。 背景 2025年3月現在、Mirador 4の開発が進められています。alpha版を以下で確認することができます。 https://github.com/ProjectMirador/mirador/releases おそらくMirador 4からの機能かと思いますが、以下のFAQで初期設定の方法が記述されています。 https://github.com/ProjectMirador/mirador/wiki/Frequently-Asked-Questions#q-how-do-i-change-the-view-of-an-image-to-zoom-to-a-certain-area 具体的には、以下のように、initialViewerConfigを用いることで、初期設定ができました。 https://github.com/ProjectMirador/mirador/blob/main/__tests__/integration/mirador/mirador-configs/initial-viewer-config.js export default { id: 'mirador', windows: [{ canvasId: 'https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174892', initialViewerConfig: { thumbnailNavigationPosition: 'far-bottom', x: 934, y: 782, // you need to specify zoom for this to look good zoom: 0.0007, }, manifestId: 'https://iiif.harvardartmuseums.org/manifests/object/299843', }], }; 応用 上記を応用して、以下のような初期設定を行ってみました。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="theme-color" content="#000000"> <title>Mirador - Table of contents</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"> </head> <body> <div id="mirador" style="position: absolute; top: 0; bottom: 0; left: 0; right: 0;"></div> <script type="module"> import Mirador from '../../../src'; // import config from './mirador-configs/initial-viewer-config.js'; const xywh = '9554.0,8213.0,1000,1000'; const spl = xywh.split(','); // Box to zoom to const boxToZoom = { height: Number(spl[3]), width: Number(spl[2]), x: Number(spl[0]), y: Number(spl[1]) }; const zoomCenter = { x: boxToZoom.x + boxToZoom.width / 2, y: boxToZoom.y + boxToZoom.height / 2 }; const config = { id: 'mirador', windows: [{ canvasId: "https://iiif.dl.itc.u-tokyo.ac.jp/repo/iiif/187cc82d-11e6-9912-9dd4-b4cca9b10970/canvas/p2", initialViewerConfig: { rotation: 180, x: zoomCenter.x, y: zoomCenter.y, zoom: 1 / Math.max(boxToZoom.width, boxToZoom.height) }, // manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', manifestId: "https://iiif.dl.itc.u-tokyo.ac.jp/repo/iiif/187cc82d-11e6-9912-9dd4-b4cca9b10970/manifest", }], }; Mirador.viewer(config); </script> </body> </html> これにより、わかりにくいですが、以下のように、180度回転しつつ、xywhにフォーカスした形で初期読み込みできました。 ...

2025年3月26日 · 1 分 · Nakamura

mdx Iのオブジェクトストレージに対するproxyサーバの構築

概要 mdx Iのオブジェクトストレージに対するproxyサーバの構築に関する備忘録です。 背景 mdx Iの利用手引きにおいて、以下のように説明されています。 https://docs.mdx.jp/ja/index.html#オブジェクトストレージ mdxが提供するS3データサービス(DDN EXAScaler S3 Data Service)で取り扱うAPI仕様を記載したDataDirect Networks社のマニュアルです。 Tipsのオブジェクトストレージの利用方法例 と合わせてご確認ください。 https://docs.mdx.jp/ja/_downloads/b5d961f2c152387fa10ed951d5278f47/S3 Data Services 5.2.7 API Reference Guide.pdf こちらをGoogle NotebookLMに読み込ませ、corsの実装状況について確認しました。以下が回答です。 回答 資料 でBucket CORS Featureが未実装 であることが明記されているため、EXAScaler Access S3 APIに対して、ウェブブラウザ上で動作するJavaScriptなどから直接、異なるオリジン (例えば、APIのエンドポイントとは異なるドメインで提供されているウェブサイトのJavaScript)を通してリクエストを行うと、CORS(Cross-Origin Resource Sharing)の問題が発生し、通常はリクエストがブロックされます 。 これは、ウェブブラウザのSame-Origin Policy というセキュリティ上の仕組みによるものです。異なるオリジンからのリソースへのアクセスは、サーバー側が明示的に許可しない限り制限されます。CORSは、この許可の仕組みを提供するものですが、EXAScaler Access S3においては、この機能がまだ実装されていないため、サーバー側からのCORSヘッダーなどのレスポンスがなく、ブラウザはクロスオリジンリクエストを拒否する と考えられます。 したがって、プロキシサーバー などを利用して、クライアントサイドのリクエストを一旦プロキシサーバーに送り、そこからEXAScaler Access S3 APIにリクエストを行うことで、オリジンが一致するように見せかけるなどの回避策が必要になる場合があります。プロキシサーバーは、APIからのレスポンスに必要なヘッダーを付与する役割を担うこともできます。 ただし、これはCORSの問題を回避するための一つの一般的な方法であり、他にもサーバーサイドでAPIを公開する際にオリジンを一致させるなどの方法も考えられます。重要なのは、EXAScaler Access S3 API自体がCORSの仕組みをサポートしていない ため、クライアントサイドから直接クロスオリジンリクエストを行う際には、何らかの対策が必要になるということです。 対策 理解が間違っている点もあるかもしれませんが、上記の回答から、mdx Iのオブジェクトストレージに対するproxyサーバの構築を行うこととしました。 構築したproxyサーバのリポジトリは以下です。 https://github.com/nakamura196/s3-proxy 例えば、以下で指定したバケット上のファイルにアクセスできます。 https://s3-proxy.vercel.app/public/CETEIcean.css 一方、以下が直接アクセスした例です。 https://s3ds.mdx.jp/satoru196/public/CETEIcean.css 前者では、以下のようなレスポンスヘッダーが確認でき、CORSの設定がなされていることが確認できます。 access-control-allow-origin: * age: 0 cache-control: public, max-age=0, must-revalidate content-encoding: br content-type: text/css date: Sun, 23 Mar 2025 03:11:27 GMT etag: W/“3d5c-psb8jsgRM3DKYLgMVwi7Ns/AIhg” server: Vercel strict-transport-security: max-age=63072000; includeSubDomains; preload x-powered-by: Express x-vercel-cache: MISS x-vercel-id: hnd1::iad1::zbdgm-1742699486592-2b286aacd062 実装 Expressを用いて、以下のように実装しました。aws-sdkについては、AWS SDK for JavaScript v3に移行する必要があるようなので、この点はご注意ください。 ...

2025年3月23日 · 2 分 · Nakamura

Next.jsでUniversal Viewerのnpmパッケージを使用する

概要 Next.jsでUniversal Viewerのnpmパッケージを使用する方法の備忘録です。 インストール 以下でインストールします。 npm i universalviewer 実装 useEffectを使うため、クライアントコンポーネントとして実装する必要があるようでした。 またdivタグにuvクラスを与えることで、cssが当たるようになりました。 'use client' import { useEffect } from 'react' import dynamic from 'next/dynamic' interface ViewerProps { manifest: string cv?: number xywh?: string } // コンポーネントの実装 function ViewerComponent({ manifest, cv, xywh }: ViewerProps) { useEffect(() => { // universalviewerをインポートして初期化 const { init } = require('universalviewer') require('universalviewer/dist/esm/index.css') init('uv', { manifest, canvasIndex: cv, xywh }) }, [manifest, cv, xywh]) return ( <div id="uv" className="uv" style={{ width: '100%', height: '60vh' }}></div> ) } // SSRを無効化し、クライアントサイドでのみレンダリングするコンポーネント const Viewer = dynamic(() => Promise.resolve(ViewerComponent), { ssr: false, loading: () => ( <div style={{ width: '100%', height: '60vh', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#f0f0f0', }} > ビューワーを読み込み中... </div> ), }) export default Viewer 他にも使用可能なオプションがあるかと思いますが、cvで処理ロードするcanvasのインデックス、xywhで表示する矩形を指定することができました。 まとめ Universal Viewerの利用にあたり、参考になりましたら幸いです。

2025年3月23日 · 1 分 · Nakamura

GakuNin RDMのAPIを用いて、連携したストレージのファイルを検索する

概要 以下の記事で、GakuNin RDMのAPIを用いたアプリケーション構築について紹介しました。 本記事では、GakuNin RDMのAPIを用いて、連携したストレージのファイルを検索する方法を紹介します。 実装例 次のような形で、検索APIを実装しました。なお、https://rdm.nii.ac.jp/api/v1/search/file/にクライアントから直接アクセスした際には、CORSによるエラーが発生したため、Next.jsのAPI Routesとして実装しています。 import { NextResponse } from "next/server"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; import { getServerSession } from "next-auth"; export async function GET(req: Request) { const session = await getServerSession(authOptions); // URLからクエリパラメータを取得 const url = new URL(req.url); const query = url.searchParams.get("filter[fulltext]") || ""; const offset = parseInt(url.searchParams.get("page[offset]") || "0", 10); const size = parseInt(url.searchParams.get("page[limit]") || "20", 10); const accessToken = session?.accessToken; const apiUrl = "https://rdm.nii.ac.jp/api/v1/search/file/"; const params = { api_version: { vendor: "grdm", version: 2 }, sort: "created_desc", highlight: "title:30,name:30,user:30,text:124,comments.*:124", elasticsearch_dsl: { query: { filtered: { query: { query_string: { default_field: "_all", fields: [ "_all", "title^4", "description^1.2", "job^1", "school^1", "all_jobs^0.125", "all_schools^0.125", ], query, analyze_wildcard: true, lenient: true, }, }, }, }, from: offset, size, }, }; const res = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(params), }); const data = await res.json(); return NextResponse.json(data); } 利用例 以下のURLからお試しいただけます。(利用にあたっては、GakuNin RDMへのログインが必要です。) ...

2025年3月21日 · 2 分 · Nakamura

GakuNin RDMのストレージに、mdx.jpのオブジェクトストレージを追加する

概要 GakuNin RDMのストレージに、mdx.jpのオブジェクトストレージを追加する方法です。 手順 mdx.jp mdx.jpのオブジェクトストレージの利用申請を行い、アクセスキーとシークレットキーを控えます。 GakuNin RDM S3 Compatible Storageを有効にします。 S3互換サービスとしてmdx S3DSを選択して、控えたアクセスキーとシークレットキーを入力します。 バケットの一覧が表示されるので、接続したいバケットを選択します。 結果として、「ファイル」メニューからアクセスできるストレージに、mdx.jpのオブジェクトストレージが追加されます。 今後、ドラッグ&ドロップにより、ファイルのアップロードなどを行うことができます。 まとめ GakuNin RDMとmdx.jpのオブジェクトストレージの接続にあたり、参考になりましたら幸いです。

2025年3月21日 · 1 分 · Nakamura

LEAF WriterとGakuNin RDMを用いたTEI/XMLファイルの編集環境の試作

概要 LEAF WriterとGakuNin RDMを用いたTEI/XMLファイルの編集環境の試作を行いましたので、備忘録です。 参考 以下の記事で、LEAF WriterをNext.jsから使用する方法を紹介しました。 特に、以下のnpmパッケージを使用しています。 https://www.npmjs.com/package/@cwrc/leafwriter 上記で編集対象とするTEI/XMLファイルの入出力にあたり、GakuNin RDMを使用してみます。GakuNin RDMのAPIをJavaScriptから使用する方法について、以下も参考になりましたら幸いです。 使い方 以下がプロトタイプシステムのURLです。(色々と不具合が含まれる点にご注意ください。) https://rdm-leaf-editor.vercel.app/ UIはClaude 3.7 Sonnetに作成してもらっています。 「サインイン」ボタンを押すと、認証画面に進むので、ログインします。 ログイン後、リダイレクトされ、プロジェクトの一覧が表示されます。 TEI/XMLファイルが含まれるディレクトリまで移動します。ファイル名に「.xml」が含まれる場合、「Leaf Writer」の列に「編集」ボタンが表示されます。 LEAF Writerの編集画面に遷移するので、テキストを編集します。作業が完了したら、画面右上の「保存」ボタンを押します。 GakuNin RDMのUIから確認してみると、バージョンごとに保存されていることが確認できます。 実装 GakuNin RDMからのファイルの取得および更新は以下で行っています。 /** * ファイルの内容を取得する */ export async function fetchFileContent( url: string, accessToken: string ): Promise<string> { const response = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${accessToken}`, }, }); if (!response.ok) { throw new Error( `ファイルの取得に失敗しました。ステータスコード: ${response.status}` ); } return await response.text(); } /** * ファイルの内容を更新する */ export async function updateFileContent( url: string, content: string, accessToken: string, contentType: string = "application/xml" ): Promise<void> { const blob = new Blob([content], { type: contentType }); const response = await fetch(url, { method: "PUT", headers: { Authorization: `Bearer ${accessToken}`, }, body: blob, }); if (!response.ok) { const errorText = await response.text(); console.error("保存に失敗しました。ステータスコード:", response.status); console.error("レスポンス:", errorText); throw new Error(`保存に失敗しました。ステータスコード: ${response.status}`); } } 上記で使用するURLは以下です。 ...

2025年3月21日 · 2 分 · Nakamura

GakuNin RDM(OSF)のAPIで、フィルタを使う

概要 GakuNin RDM(OSF)のAPIで、フィルタを使う方法の備忘録です。 対象データ 以下のようなファイル構造を持つ「NII Storage」を対象にします。 APIでは、以下のようなURLでアクセスできるものを対象にします。 https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/files/osfstorage/ JSONデータの例は以下です。 { "data": [ { "id": "67ce5b0b2fe4740010f753c0", "type": "files", "attributes": { "guid": "ungd3", "checkout": null, "name": "IMG_8269.png", "kind": "file", "path": "/67ce5b0b2fe4740010f753c0", "size": 952107, "provider": "osfstorage", "materialized_path": "/IMG_8269.png", "last_touched": null, "date_modified": "2025-03-10T03:22:51.750550Z", "date_created": "2025-03-10T03:22:51.750550Z", "extra": { "hashes": { "md5": "e57192b30103a7e995597ceaea39cbbf", "sha256": "5e282187067a53aaab0f1f00daaefb9519d60b064831403e671662cfbcf6f41f" }, "downloads": 0 }, "tags": [], "current_user_can_comment": true, "current_version": 1 }, "relationships": { "parent_folder": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/674034a483bdc200108b8a95/?format=json", "meta": {} } }, "data": { "id": "674034a483bdc200108b8a95", "type": "files" } }, "versions": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/versions/?format=json", "meta": {} } } }, "comments": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/comments/?format=json&filter%5Btarget%5D=ungd3", "meta": {} } } }, "metadata_records": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/metadata_records/?format=json", "meta": {} } } }, "node": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/?format=json", "meta": {} } }, "data": { "id": "wzv9g", "type": "nodes" } }, "target": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/", "meta": { "type": "node" } } }, "data": { "type": "node", "id": "wzv9g" } } }, "links": { "info": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/", "move": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67ce5b0b2fe4740010f753c0", "upload": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67ce5b0b2fe4740010f753c0", "delete": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67ce5b0b2fe4740010f753c0", "download": "https://rdm.nii.ac.jp/download/ungd3/", "render": "https://mfr.rdm.nii.ac.jp/render?url=https://rdm.nii.ac.jp/download/ungd3/?direct%26mode=render", "html": "https://rdm.nii.ac.jp/wzv9g/files/osfstorage/67ce5b0b2fe4740010f753c0", "self": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/" } }, { "id": "67da847416000900109e0454", "type": "files", "attributes": { "guid": "b45mp", "checkout": null, "name": "01.xml", "kind": "file", "path": "/67da847416000900109e0454", "size": 79397, "provider": "osfstorage", "materialized_path": "/01.xml", "last_touched": null, "date_modified": "2025-03-19T13:24:27.868078Z", "date_created": "2025-03-19T08:46:44.636107Z", "extra": { "hashes": { "md5": "a3824b2f49471842d1046a2abe623284", "sha256": "83d18a6e52a52597ebac6fa1eb8a137ed6e1e64b9c0e2c1a0b49cf746a777d0a" }, "downloads": 0 }, "tags": [], "current_user_can_comment": true, "current_version": 5 }, "relationships": { "parent_folder": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/674034a483bdc200108b8a95/?format=json", "meta": {} } }, "data": { "id": "674034a483bdc200108b8a95", "type": "files" } }, "versions": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/versions/?format=json", "meta": {} } } }, "comments": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/comments/?format=json&filter%5Btarget%5D=b45mp", "meta": {} } } }, "metadata_records": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/metadata_records/?format=json", "meta": {} } } }, "node": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/?format=json", "meta": {} } }, "data": { "id": "wzv9g", "type": "nodes" } }, "target": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/", "meta": { "type": "node" } } }, "data": { "type": "node", "id": "wzv9g" } } }, "links": { "info": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/", "move": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67da847416000900109e0454", "upload": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67da847416000900109e0454", "delete": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67da847416000900109e0454", "download": "https://rdm.nii.ac.jp/download/b45mp/", "render": "https://mfr.rdm.nii.ac.jp/render?url=https://rdm.nii.ac.jp/download/b45mp/?direct%26mode=render", "html": "https://rdm.nii.ac.jp/wzv9g/files/osfstorage/67da847416000900109e0454", "self": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/" } }, { "id": "67daca9916000900109e1d98", "type": "files", "attributes": { "guid": null, "checkout": null, "name": "test", "kind": "folder", "path": "/67daca9916000900109e1d98/", "size": null, "provider": "osfstorage", "materialized_path": "/test/", "last_touched": null, "date_modified": null, "date_created": null, "extra": { "hashes": { "md5": null, "sha256": null } }, "tags": [], "current_user_can_comment": true, "current_version": 1 }, "relationships": { "parent_folder": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/674034a483bdc200108b8a95/?format=json", "meta": {} } }, "data": { "id": "674034a483bdc200108b8a95", "type": "files" } }, "files": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/files/osfstorage/67daca9916000900109e1d98/?format=json", "meta": {} } } }, "node": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/?format=json", "meta": {} } }, "data": { "id": "wzv9g", "type": "nodes" } }, "target": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/", "meta": { "type": "node" } } }, "data": { "type": "node", "id": "wzv9g" } } }, "links": { "info": "https://api.rdm.nii.ac.jp/v2/files/67daca9916000900109e1d98/", "move": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/", "upload": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/", "delete": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/", "new_folder": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/?kind=folder", "self": "https://api.rdm.nii.ac.jp/v2/files/67daca9916000900109e1d98/" } } ], "links": { "first": null, "last": null, "prev": null, "next": null, "meta": { "total": 3, "per_page": 10 } } } 検索例 JSON:APIに準拠しているので、filterパラメータを使用します。 ...

2025年3月19日 · 4 分 · Nakamura

Nuxt Content: Cannot find name 'queryContent'.への対応

概要 Nuxt Contentにおいて、「Cannot find name ‘queryContent’.」というエラーが発生しましたので、対処方法に関する備忘録です。 原因 2025/1/16にNuxt Content v3がリリースされたようです。 https://content.nuxt.com/blog/v3 これにより、queryContentはqueryCollectionなどに変更されたようです。 対処方法 以下に記載があるように、content.config.tsを作成した上で、queryCollectionなどを使用するように変更する必要があるようです。 https://content.nuxt.com/blog/v3#️-content-collections 上記の対応により、エラーを解消できました。 まとめ Nuxt Content v2からv3への移行にあたり、参考になりましたら幸いです。

2025年3月19日 · 1 分 · Nakamura

IIIF georeference extensionの可視化ツールの改修

概要 IIIF georeference extensionの可視化ツールの改修を行いましたので備忘録です。 以下で公開しているツールです。 https://github.com/nakamura196/iiif_geo 以下のように、現代地図と画像の並列表示機能を提供します。 改修内容 IIIF georeference extensionに基づくデータ作成を支援するツールとして、Allmapsがあります。 https://allmaps.org/ 以下で使い方を紹介しています。 今回の改修では、上記ツールで作成されるデータを読み込めるようにしました。 https://annotations.allmaps.org/images/2e1d3f991aad6cb4 以下が表示例です。 https://nakamura196.github.io/iiif_geo/ja?u=https://annotations.allmaps.org/images/2e1d3f991aad6cb4 まとめ IIIF georeference extensionの応用にあたり、参考になりましたら幸いです。

2025年3月19日 · 1 分 · Nakamura

AWSのRoute 53で設定したレコードを、さくらレンタルサーバで使用する(共有SSL)

概要 AWSのRoute 53で設定したレコードを、さくらレンタルサーバで使用する備忘録です。加えて、Let’s Encryptを用いて、無料SSLを使用します。 さくらレンタルサーバ ドメイン/SSLにアクセスして、「ドメイン新規追加」ボタンを押します。 画面下部の「他社で取得したドメインを移管せずに使う」の「追加」ボタンを押します。 独自ドメインを入力して、「追加」ボタンを押します。以下の例では、「aaa.example.org」としています。 追加後、追加したドメイン名の「設定」>「DNSレコード設定」を押し、AレコードのIPアドレスを控えます。 AWSのRoute 53 先ほど控えたIPアドレスを使って、レコードを追加します。 さくらレンタルサーバ SSLの設定を行います。 「SSL証明書の種類を選択」ボタンを押します。 「Let’s Encrypt (無料SSL)」の「利用する」ボタンを押します。 設定後、以下のような画面になります。少し待ちます。 その後、ドメイン設定において、以下のように設定します。 さらに、それぞれのドメインに対して、「WEB公開フォルダ」を設定することで、ルートディレクトリを変更することができました。 まとめ AWSのRoute 53以外でも同様の手続きを行うことができると思います。 間違っている点もあるかもしれませんが、さくらレンタルサーバでの独自ドメインおよびSSLの利用にあたり、参考になりましたら幸いです。

2025年3月18日 · 1 分 · Nakamura

TEI/XMLから検索システムを構築する際のDTS(Distributed Text Services)のdts:wrapperの応用例

概要 TEI/XMLから検索システムを構築する際のDTS(Distributed Text Services)のdts:wrapperタグの応用例に関するメモです。 DTS(Distributed Text Services)は以下です。 Cayless, H., Clérice, T., Jonathan, R., Scott, I., & Almas, B. Distributed Text Services Specifications (Version 1-alpha) [Computer software]. https://github.com/distributed-text-services/specifications` 参考 DTSの構築例として、以下なども参考になりましたら幸いです。 例 以下の「デジタル延喜式」を例とします。 https://khirin-t.rekihaku.ac.jp/engishiki/ 本システムでは、TEIを用いて作成したXMLデータから、検索時の単位となる部分を抽出し、それをJSON形式のデータに変換した上で検索を行っています。JSONデータの例は以下です。JSON:APIに準拠した記述を採用しています。 http://khirin-t.rekihaku.ac.jp/engishiki/jsonapi/item/39100101.json { "jsonapi": { "version": "1.0", "meta": { "links": { "self": { "href": "http://jsonapi.org/format/1.0/" } } } }, "data": { "type": "item", "id": "39100101", "attributes": { "label": "正親 1 諸王年満条 項1", "jyo": [ "39-1-001 諸王年満" ], "shiki": [ "39-1 正親" ], "vol": [ "39" ], "updated": "2025-03-15", "category": [ "式" ], "manifest": "https://khirin-a.rekihaku.ac.jp/iiif/rekihaku/H-743-74-39/manifest.json", "member": "https://khirin-a.rekihaku.ac.jp/iiif/2/engishiki%2FH-743-74-39/page5069", "thumbnail": "https://khirin-a.rekihaku.ac.jp/iiif/2/engishiki%2FH-743-74-39%2F00002.tif/full/200,/0/default.jpg", "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<TEI xmlns=\"http://www.tei-c.org/ns/1.0\">\n <dts:wrapper xmlns:dts=\"https://w3id.org/api/dts#\">\n <div type=\"original\"><p ana=\"項\" corresp=\"#page5069\" xml:id=\"o-item39100101\">\n 凡諸王年満十二、毎年十二月、\n<orgName sameAs=\"#京職\">京職</orgName>\n移\n<orgName sameAs=\"#宮内省\">宮内省</orgName>\n、\n<orgName sameAs=\"#宮内省\">省</orgName>\n以\n<orgName sameAs=\"#京職\">京職</orgName>\n移、即付\n<orgName sameAs=\"#正親司\">司</orgName>\n令勘会名簿、訖更送\n<orgName sameAs=\"#宮内省\">省</orgName>\n、明年正月待\n<orgName sameAs=\"#太政官\">官</orgName>\n符到、始預賜時服之例、</p></div><div type=\"japanese\"><note type=\"summary\">\n 衣替え手当ての受給年齢に達する皇族への支給開始手続きに関する規定\n </note><p ana=\"項\" corresp=\"engishiki_v39.xml#item39100101 engishiki_v39_en.xml#item39100101\" xml:id=\"ja-item39100101\">\n 皇族の年齢が数えで十二歳に達したら、十二月に\n<ruby>\n<rb>\n 京職\n </rb>\n<rt place=\"right\">\n きょうしき\n </rt>\n</ruby>\n が\n<ruby>\n<rb>\n 宮内省\n </rb>\n<rt place=\"right\">\n くないしょう\n </rt>\n</ruby>\n に通知し、宮内省は京職の通知書類を\n<ruby>\n<rb>\n 正親司\n </rb>\n<rt place=\"right\">\n せいしんし\n </rt>\n</ruby>\n に下して正親司が保管する皇族の台帳と照合させよ。正親司はこの作業が終わったら通知書類を宮内省に送れ。翌年正月に\n<ruby>\n<rb>\n 太政官\n </rb>\n<rt>\n だいじょうかん\n </rt>\n</ruby>\n の通達を受領してから、衣替え手当ての支給を開始せよ。\n</p></div><div type=\"english\"><note type=\"summary\">\n Age of Royal Recipients for Seasonal Clothing\n </note><p ana=\"項\" corresp=\"engishiki_v39.xml#item39100101 engishiki_v39_ja.xml#item39100101\" xml:id=\"en-item39100101\">\n Every year if\n<seg xml:id=\"footnote3910010101\">\n a\n prince or princess\n </seg>\n reaches\n<seg xml:id=\"footnote3910010102\">\n twelve years old\n </seg>\n , the Capital Office should report that\n information via\n<seg xml:id=\"footnote3910010103\">\n a parallel memorandum\n (\n<seg rend=\"italic\">\n i\n </seg>\n )\n</seg>\n to the Ministry of the Royal\n Household in the twelfth month. Then the Ministry should send the\n memorandum to the Royal Family Register Office to check the list against\n their existing roster. After finishing all of these procedures, the\n Royal Family Register Office should return the memorandum to the\n Ministry. The prince or princess in question will receive seasonal\n clothing after the Council of State's order is issued in the following\n New Year.\n</p></div>\n </dts:wrapper>\n</TEI>" } } } 検索結果は以下のように表示されます。校訂文(@type=“original”)、現代語訳(@type=“japanese”)、および英訳(@type=“english”)を表示しています。 ...

2025年3月15日 · 2 分 · Nakamura

Miradorで画像を表示し、CETEIceanでテキストを表示するサンプルアプリ

概要 TEI/XMLファイルを読み込み、Miradorで画像を表示し、CETEIceanでテキストを表示するサンプルアプリを作成しました。以下のURLからお試しいただけます。 デモサイト https://nakamura196.github.io/ceteicean-mirador/ 背景 これまでにも、同様の機能を提供するアプリケーションを開発してきました。 Next.js を使用した実装例 XSLT を使用した実装例 今回は、HTMLとプレーンなJavaScriptのみを使用して実装する方法をご紹介します。 対象データ 以下の校異源氏物語テキストDBを対象とします。 https://kouigenjimonogatari.github.io/ 実装方法 ソースコードは以下のリポジトリで公開しています。 https://github.com/nakamura196/ceteicean-mirador 実装のポイント 1. CETEIcean の behaviors を利用した pb タグの処理 以下のコードでは、CETEIcean の behaviors を利用して pb タグのクリック時の挙動を定義しています。 ct.addBehaviors({ tei: { // 不要な要素を非表示 graphic: () => document.createDocumentFragment(), figure: () => document.createDocumentFragment(), // pbタグの処理 pb: function (el) { let pb = document.createElement("tei-pb"); // 属性を引き継ぐ if (el.hasAttribute("n")) { pb.setAttribute("n", el.getAttribute("n")); } // corresp属性からzoneIdを取得 const corresp = el.getAttribute("corresp"); if (corresp) { const zoneId = corresp.replace('#', ''); pb.setAttribute("data-zone-id", zoneId); } // ページ番号を表示 const pageNum = el.getAttribute("n") || ""; pb.textContent = `[Page ${pageNum}]`; // クリックイベントを追加 pb.addEventListener("click", function() { const zoneId = this.getAttribute("data-zone-id"); if (zoneId) { const zoneElement = document.querySelector(`tei-zone[id="${zoneId}"]`); if (zoneElement) { const surfaceElement = zoneElement.closest("tei-surface"); if (surfaceElement && surfaceElement.hasAttribute("sameAs")) { goToPage(surfaceElement.getAttribute("sameAs")); } } } }); return pb; } }, }); 2. Mirador でのページ遷移処理 pb タグをクリックした際に、TEI/XML ファイルから Canvas の URI を取得し、Mirador のページ遷移を実行します。 ...

2025年3月14日 · 1 分 · Nakamura

Omeka Sで非公開サイトを共有する

概要 Omeka Sで非公開サイトを共有する機会があったので、備忘録です。 関連モジュール まず以下のモジュールを確認しました。 https://omeka.org/s/modules/Guest/ 上記では、Guest Privateモジュールも紹介されており、使い分けの方法をChatGPTに問い合わせました。 Omeka S の Guest モジュール と Guest Private モジュール の使い分けについて説明します。 1. Guest モジュール guest ロール を追加し、ゲストユーザーとして登録できるようにする。 管理画面へのアクセス権限はなし 。 API経由でログイン・ログアウト・登録・パスワード更新などが可能。 公開サイトのみ閲覧可能 (非公開サイトや非公開ページは見られない)。 用途 : 公開サイトに一般ユーザーを登録させ、ログイン機能を提供する。 2. Guest Private モジュール このモジュールを追加すると、さらに2つのロールが追加される: (1) guest_private_site 公開サイト + 非公開サイトを閲覧可能 (ただし、非公開ページや非公開リソースは見られない)。 用途 : サイト全体を非公開にしつつ、特定のユーザーにサイトの閲覧権限を与えたい場合。 (2) guest_private 公開サイト + 非公開サイト + 非公開ページ + 非公開リソースを閲覧可能 。 用途 : 非公開リソースを含む、特定のコンテンツを許可されたゲストユーザーだけに見せたい場合。 質問: 非公開サイトに指定したユーザーだけ閲覧できるようにしたい 解決策 Guest Private モジュールをインストールする → 通常の Guest モジュールでは非公開サイトの閲覧ができないため、Guest Private が必要。 ...

2025年3月13日 · 1 分 · Nakamura

AtoM(Access to Memory)のAPIを使って、オブジェクトを登録してみる

概要 AtoM(Access to Memory)のAPIを使って、オブジェクトを登録する方法の備忘録です。 APIの有効化 以下にアクセスします。 /sfPluginAdminPlugin/plugins arRestApiPluginを有効にします。 APIキーの取得 以下に、APIキーを生成する方法が説明されています。 https://www.accesstomemory.org/en/docs/2.9/dev-manual/api/api-intro/#generating-an-api-key-for-a-user ユーザ名とパスワードでもAPI接続できるようですが、今回はREST API Keyを発行しました。 エンドポイント AtoMでは、「典拠レコード」や「機能」など、複数のメニューが提供されていますが、APIによって利用できるのは、以下のみのようです。 See the subsequent pages for more details on each endpoint, and available parameters. There are three endpoints available: Browse taxonomy terms Browse information objects Read information object Download digital objects Add physical objects この点は、ArchivesSpaceのほうが豊富なAPIが提供されており、軍配が上がるかもしれません。 https://archivesspace.github.io/archivesspace/api/ また、以下のソースコードを確認すると、CreateActionが可能なものは、informationobjectsとphysicalobjects、digitalobjectsに限定されているようでした。 https://github.com/artefactual/atom/tree/qa/2.x/plugins/arRestApiPlugin/modules/api/actions ただ機械的に一括登録を行いたい場面は、主にinformationobjectsだと考えられるため、これらの機能のみで十分かもしれません。 physical objectsの登録 以下のようなクラスを用意します。 #| export class ApiClient: def __init__(self): load_dotenv(override=True) self.url = os.getenv("atom_url") username = os.getenv("username") password = os.getenv("password") api_key = os.getenv("api_key") if api_key: self.headers = { "REST-API-Key": api_key, "Content-Type": "application/json" } else: # Basic 認証のヘッダーを作成 auth_string = f"{username}:{password}" auth_bytes = auth_string.encode('ascii') auth_b64 = base64.b64encode(auth_bytes).decode('ascii') self.headers = { "Authorization": f"Basic {auth_b64}", "Content-Type": "application/json" } def add_physical_objects(self, physical_objects): url = f"{self.url}/api/physicalobjects" print(url, self.headers, physical_objects) response = requests.post(url, headers=self.headers, json=physical_objects) # レスポンスを確認 if response.status_code in [200, 201]: print("物理オブジェクトが作成されました!") print(f"ステータスコード: {response.status_code}") print(f"レスポンス: {response.text}") # 作成されたオブジェクトの情報 result = response.json() print(f"作成された物理オブジェクトID: {result.get('id')}") print(json.dumps(result, indent=4)) else: print(f"エラー: {response.status_code}") print(f"レスポンス: {response.text}") 以下で実行します。 ...

2025年3月12日 · 2 分 · Nakamura

AtoM(Access to Memory)をDockerで起動する

概要 AtoM(Access to Memory)をDockerで起動する機会があったので、備忘録です。 マニュアル 以下に記載があります。 https://www.accesstomemory.org/es/docs/2.9/dev-manual/env/compose/ git clone -b qa/2.x https://github.com/artefactual/atom.git atom cd atom export COMPOSE_FILE="$PWD/docker/docker compose.dev.yml" docker compose up -d そして、以下を実行します。 docker compose exec atom php symfony tools:purge --demo これにより、63001ポートでAtoMが起動しました。 その他、マニュアルにはCompile Theme Filesの記述などがありますが、これを実行しなくても起動する場合と起動しない場合がありました。 日本語化 以下にアクセスして、日本語を追加します。 /settings/language そして、以下を実行します。 docker compose exec atom php symfony search:populate 結果、以下のように日本語が追加されました。 まとめ AtoMの利用にあたり、参考になりましたら幸いです。

2025年3月12日 · 1 分 · Nakamura