How to Add Dark Mode Using Tailwind CSS V4 with Next.js 15 App Router

Overview This article describes how to add dark mode using Tailwind CSS V4 with the Next.js 15 App Router. The following article was helpful as a reference. https://sujalvanjare.vercel.app/blog/dark-mode-nextjs15-tailwind-v4 I was able to switch between dark mode and light mode as shown below. The following is a Japanese translation of the above article by ChatGPT. Learn how to implement dark mode and light mode using Tailwind CSS V4 in a Next.js 15 App Router project. This step-by-step guide covers the latest changes in Tailwind V4 and Next.js 15 to achieve a seamless theme switcher! ...

April 1, 2025 · 10 min · Nakamura

Handling the Error: Do not use <img>. Use Image from 'next/image' instead.

Overview The following article was helpful for handling the error: “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 Based on the article above, I am posting a ChatGPT-generated response below. There may be some inaccuracies, but I hope it serves as a helpful reference. Changing Next.js ESLint Rules and Configuring Flat Config Since Next.js 11, ESLint configuration has been provided by default, and the @next/next/no-img-element rule was added. This rule restricts the use of regular <img> tags and recommends using Next.js’s next/image component. ...

April 1, 2025 · 2 min · Nakamura

Omeka S Module Update Information (2025-03-27)

Overview This article introduces modules that required updates during the operation of Omeka S. IIIF Server https://omeka.org/s/modules/IiifServer/ I had been using version 3.6.18, released in February 2024, but a bug was found where thumbnail images were rendered incorrectly during IIIF manifest file generation. After updating to the latest version 3.6.24 as of March 2025, this bug was resolved. Note that this update also required updating the Common module, so I hope this information is helpful. ...

March 27, 2025 · 1 min · Nakamura

Scrolling to a Specific Element Using CETEIcean and XPath

Overview This is a memo on how to scroll to a specific element using CETEIcean and XPath. Demo You can try it at the following URL. https://next-ceteicean-router.vercel.app/xpath/ After accessing the page and scrolling, it is displayed as follows. Obtaining the XPath The above example targets the XML file from the “Koui Genji Monogatari Text DB.” https://kouigenjimonogatari.github.io/tei/01.xml The following XPath is specified. /TEI/text[1]/body[1]/p[1]/seg[267] To obtain this XPath, you can right-click on the target element in Oxygen XML Editor and select “Copy XPath.” ...

March 27, 2025 · 1 min · Nakamura

Mirador 4 Plugin Development: Enabling Initial Angle Settings in the Image Rotation Plugin

Overview I enabled initial angle settings in the Mirador 4 plugin for rotating images at arbitrary angles. The repository is here. https://github.com/nakamura196/mirador-rotation-plugin The demo page is here. You can rotate images with initial settings for angle and bounding box. https://nakamura196.github.io/mirador-rotation-plugin/ Background The following article explains this plugin. However, there was an issue where initial angle values could not be provided. As introduced in the following article, it appeared that Mirador 4’s standard functionality allows providing initial angle values. ...

March 26, 2025 · 3 min · Nakamura

Rotating Images and Specifying Regions on Initial Load in Mirador 4

Overview I introduce how to rotate images and specify regions on initial load in Mirador 4. Background As of March 2025, development of Mirador 4 is underway. The alpha version can be checked at the following link. https://github.com/ProjectMirador/mirador/releases This is probably a feature from Mirador 4, and the following FAQ describes the initial configuration method. 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 Specifically, as shown below, initial configuration was achieved by using 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', }], }; Application Building on the above, I tried the following initial configuration. ...

March 26, 2025 · 3 min · Nakamura

Building a Proxy Server for mdx I Object Storage

Overview This is a memo on building a proxy server for mdx I’s object storage. Background The mdx I user guide explains the following. https://docs.mdx.jp/ja/index.html#オブジェクトストレージ This is a DataDirect Networks manual describing the API specifications handled by the S3 Data Service (DDN EXAScaler S3 Data Service) provided by mdx. Please review it together with the object storage usage examples in Tips. https://docs.mdx.jp/ja/_downloads/b5d961f2c152387fa10ed951d5278f47/S3 Data Services 5.2.7 API Reference Guide.pdf ...

March 23, 2025 · 3 min · Nakamura

Using the Universal Viewer npm Package in Next.js

Overview Here are my notes on how to use the Universal Viewer npm package in Next.js. Installation Install with the following command. npm i universalviewer Implementation Since it uses useEffect, it appeared necessary to implement it as a client component. Also, by adding the uv class to the div tag, the CSS was applied correctly. 'use client' import { useEffect } from 'react' import dynamic from 'next/dynamic' interface ViewerProps { manifest: string cv?: number xywh?: string } // Component implementation function ViewerComponent({ manifest, cv, xywh }: ViewerProps) { useEffect(() => { // Import and initialize 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> ) } // Component that disables SSR and renders only on the client side const Viewer = dynamic(() => Promise.resolve(ViewerComponent), { ssr: false, loading: () => ( <div style={{ width: '100%', height: '60vh', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#f0f0f0', }} > Loading viewer... </div> ), }) export default Viewer There may be other available options, but I was able to specify the canvas index to initially load with cv and the display rectangle with xywh. ...

March 23, 2025 · 2 min · Nakamura

Searching Files in Linked Storage Using the GakuNin RDM API

Overview In the following article, I introduced building applications using the GakuNin RDM API. In this article, I introduce how to search files in linked storage using the GakuNin RDM API. Implementation Example I implemented the search API as follows. Since directly accessing https://rdm.nii.ac.jp/api/v1/search/file/ from the client caused CORS errors, I implemented it as a Next.js API Route. 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); // Get query parameters from 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); } Usage Example You can try it at the following URL. (Login to GakuNin RDM is required.) ...

March 21, 2025 · 2 min · Nakamura

Adding mdx.jp Object Storage to GakuNin RDM Storage

Overview This article describes how to add mdx.jp object storage to GakuNin RDM storage. Procedure mdx.jp Submit a usage application for mdx.jp object storage and note down the access key and secret key. GakuNin RDM Enable S3 Compatible Storage. Select mdx S3DS as the S3-compatible service and enter the access key and secret key you noted earlier. A list of buckets will be displayed. Select the bucket you want to connect to. ...

March 21, 2025 · 1 min · Nakamura

Prototyping a TEI/XML File Editing Environment Using LEAF Writer and GakuNin RDM

Overview This is a memo about prototyping a TEI/XML file editing environment using LEAF Writer and GakuNin RDM. References The following article introduced how to use LEAF Writer from Next.js. In particular, the following npm package is used. https://www.npmjs.com/package/@cwrc/leafwriter For the input/output of TEI/XML files to be edited above, GakuNin RDM is used. The following article may also be helpful regarding how to use the GakuNin RDM API from JavaScript. ...

March 21, 2025 · 2 min · Nakamura

Using Filters with the GakuNin RDM (OSF) API

Overview This is a memo on how to use filters with the GakuNin RDM (OSF) API. Target Data We target “NII Storage” with the following file structure. Via the API, we target data accessible at URLs like the following. https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/files/osfstorage/ An example of the JSON data is shown below. { "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 } } } Search Examples Since it conforms to JSON:API, the filter parameter is used. ...

March 19, 2025 · 4 min · Nakamura

Nuxt Content: Addressing 'Cannot find name queryContent'

Overview This is a memo on how to address the error “Cannot find name ‘queryContent’.” that occurred in Nuxt Content. Cause It appears that Nuxt Content v3 was released on 2025/1/16. https://content.nuxt.com/blog/v3 As a result, queryContent has been changed to queryCollection and similar. Solution As described in the following, it appears necessary to create a content.config.ts file and change to using queryCollection and similar. https://content.nuxt.com/blog/v3#️-content-collections By applying the above changes, the error was resolved. ...

March 19, 2025 · 1 min · Nakamura

Updates to the IIIF Georeference Extension Visualization Tool

Overview This is a memo about updates made to the IIIF georeference extension visualization tool. The tool is published at the following location. https://github.com/nakamura196/iiif_geo It provides a side-by-side display of modern maps and images, as shown below. Update Details Allmaps is a tool that supports data creation based on the IIIF georeference extension. https://allmaps.org/ Its usage is introduced in the following article. In this update, support was added to load data created by the above tool. ...

March 19, 2025 · 1 min · Nakamura

Using Route 53 DNS Records with Sakura Rental Server (Shared SSL)

Overview This is a memo on using DNS records configured in AWS Route 53 with Sakura Rental Server. Additionally, we use free SSL with Let’s Encrypt. Sakura Rental Server Go to Domain/SSL and click the “Add New Domain” button. Click the “Add” button under “Use a domain acquired from another provider without transferring” at the bottom of the screen. Enter the custom domain and click the “Add” button. In the following example, “aaa.example.org” is used. ...

March 18, 2025 · 1 min · Nakamura

Application of DTS (Distributed Text Services) dts:wrapper When Building Search Systems from TEI/XML

Overview This is a note on the application of the DTS (Distributed Text Services) dts:wrapper tag when building search systems from TEI/XML. DTS (Distributed Text Services) is described as follows: 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` References As an example of building DTS, the following may also be helpful. Example The following “Digital Engishiki” is used as an example. ...

March 15, 2025 · 3 min · Nakamura

A Sample App Displaying Images with Mirador and Text with CETEIcean

Overview I created a sample app that loads TEI/XML files, displays images with Mirador, and displays text with CETEIcean. You can try it from the following URL. Demo Site https://nakamura196.github.io/ceteicean-mirador/ Background I have previously developed applications that provide similar functionality. Implementation example using Next.js Implementation example using XSLT This time, I introduce an approach using only HTML and plain JavaScript. Target Data The target is the following Koui Genji Monogatari Text DB. ...

March 14, 2025 · 2 min · Nakamura

Sharing Private Sites in Omeka S

Overview I had the opportunity to share a private site in Omeka S, so this is a personal note for future reference. Related Modules First, I checked the following module. https://omeka.org/s/modules/Guest/ The above also introduces the Guest Private module, and I asked ChatGPT about how to differentiate between them. Here is an explanation of the differences between the Guest module and the Guest Private module in Omeka S. 1. Guest Module Adds the guest role, allowing users to register as guest users. No access to the admin panel. Login, logout, registration, password update, etc. are possible via API. Only public sites are viewable (private sites and private pages cannot be seen). Use case: Register general users on a public site and provide login functionality. 2. Guest Private Module Adding this module provides two additional roles: ...

March 13, 2025 · 2 min · Nakamura

Registering Objects Using the AtoM (Access to Memory) API

Overview This is a memo on how to register objects using the AtoM (Access to Memory) API. Enabling the API Access the following. /sfPluginAdminPlugin/plugins Enable arRestApiPlugin. Obtaining an API Key The following explains how to generate an API key. https://www.accesstomemory.org/en/docs/2.9/dev-manual/api/api-intro/#generating-an-api-key-for-a-user While it appears you can also connect to the API with a username and password, this time I issued a REST API Key. Endpoints AtoM provides multiple menus such as “Authority records” and “Functions,” but it appears that only the following are available via the API. ...

March 12, 2025 · 4 min · Nakamura

Running AtoM (Access to Memory) with Docker

Overview I had the opportunity to run AtoM (Access to Memory) with Docker, so here are my notes. Manual The documentation is available at the following link. 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 Then, run the following. docker compose exec atom php symfony tools:purge --demo This started AtoM on port 63001. Additionally, the manual includes instructions for Compile Theme Files, but there were cases where it started without running this and cases where it did not. ...

March 12, 2025 · 1 min · Nakamura