この記事を作った動機
2025/10/2 から、2025/10/9 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。
動作の様子 (フロントエンド)
TODOs
2025/10/9 になる間やる項目たち。やりきれなかった分は次に持ち越し。
主な項目
- ノートブック作成機能のバグチェックと修正
- ノートブック、ページ関連における作成、削除、コピー名前の変更があった時、selector の情報やページviewを更新するようにする
- ページ作成機能の実装
- フロントエンド
- バックエンド
- 新しい階層構造を指定できるようにする
- linux だけでなく windows 環境において、再帰的にフォルダを作成できるようにする。
- ノートブック削除機能の実装
- フロントエンド
- バックエンド
- ページ削除機能の実装
- フロントエンド
- バックエンド
- markdown における編集機能、データセーブ(更新)の実装
- フロントエンド (buggy)
- バックエンド
- Selector に未保存の表示を行う
- 別のページを開いても未保存のデータのバッファが管理されるようにする
- 内部構造を説明する図を作る
- フロントエンド
- バックエンド
- free ページの実装
- フロントエンド
- バックエンド
他の事項
- File API 周りの実装
- マルチメディアの対応
- messagebox の実装
やったことの詳細
ページ作成機能の実装
- フォームを実装した。
とりあえずフロントエンド周りのネットワーク関連も実装して、あとはバックエンドのみ。- バックエンド、フロントエンド両方とも、とりあえず動くところまでできた。
ノートブック削除機能の実装
- フォームを実装した。
とりあえずフロントエンド周りのネットワーク関連も実装して、あとはバックエンドのみ。- バックエンド、フロントエンド両方とも、とりあえず動くところまでできた。
ページ削除機能の実装
- フォームを実装した。
とりあえずフロントエンド周りのネットワーク関連も実装して、あとはバックエンドのみ。- バックエンド、フロントエンド両方とも、とりあえず動くところまでできた。
編集機能、データセーブ(更新)の実装
全体
- 編集機能は、各ページで独自実装するようにした。
ToggleToolsBarにedit項目を追加し、各ページの独自実装の機能を呼び出せるようにした。-> 不具合の温床になっているっぽかったので、toggleable を設定したコンポーネントが自分で、コンポーネントが消されるときに toggleable を外すようにした。page.tsxにて、新しいページを開いたときは、ToggleToolsBarのeditは新しいページが新たにツールを登録するためにtoggleableのクリーンアップだけするようにした。- データサーバー側は、単純にフロントエンドが送りつけた文字列をセーブデータとして扱う簡易的な仕様にすることにした。一応最低限のメタデータが存在するかはチェックするが、データ本体の整合性に関しては、フロントエンドに責任があることと現状ではしている。
- とりあえず、markdown 形式のページについては動作するところまで
updatePageコマンドを実装し終えた。ただ、freeページに代表される、json 形式のページも実装はあるがまだ動くかテストできるほどフロントエンドが完成していないため、それについてはあとからテストする必要がある。
markdown ページ
- preview splitView editorView を選択できるようにした。
- 取得した markdown データは直接扱わず、
markdownBufferという状態変数によって中間的に管理することで、編集時に変更が反映できる仕組みにした。 - highlight.js を使って、マークダウンをハイライトしようとした。
- とりあえず、ctrl + S を押したり、セーブボタンを押せば、本当にページが保存されるところまではできた。
内部構造を説明する図を作る
- とりあえずバックエンドは、現状の範囲で書き出した。
ページの基本情報を表示
作成当時
ページに付いているタグやファイルなどを管理するUIを作った。まだボタンがあるだけで、ファイルのプレビューを開いたり、タグの追加や削除、タグに付随するページをリスティングする機能は付いていないが、一応形は作ってみた。
変更点1
UUID の代わりに、ページの作成日時と更新日時を表示するようにしてみた。
ページのメタデータ情報を追加
作成日と作成時を記録するようにした。
古い形式
++++
{
"files":["なんかよくわかんない絵ジト目口開き3.png"],
"tags":[],
"UUID":"6428f4c7-7622-40b9-a6bc-f895b3f9ada8"
}
++++
新しい形式
++++
{
"files":["なんかよくわかんない絵ジト目口開き3.png"],
"tags":[],
"createDate": "2025/10/6",
"updateDate": "2025/10/6",
"UUID":"6428f4c7-7622-40b9-a6bc-f895b3f9ada8"
}
++++
バックエンドのヘルパー関数を追加
特定のノートブックのメタデータを更新するための専用の関数を作った。主に以下のことを確認した後に、データを更新する。
- メタデータ自体が存在しているか
- バックアップは作成できたか
- メタデータは更新できたか
# arg:
# notebookName : notebook name
# notebookMatadata : entire updated notebook metadata
# return value
# OK : False
# Error : True will be returned when there are no notebooks or other type error is occured.
def updateNotebookMatadata(notebookName:str,notebookMatadata:dict):
notebookMatadata['updateDate'] = timeString()
notebookStoreRoot = loadSettings.settings["NotebookRootFolder"][0]
notebookMetadataPath = notebookStoreRoot + "/" + notebookName + "/metadata.json"
print("updateNotebookMatadata info: ")
print(notebookMatadata)
print(notebookName)
print(notebookMetadataPath)
if(not os.path.exists(notebookMetadataPath)):
print("updateNotebookMatadata ERROR: the metadata file does not exist.")
return True
# https://stackoverflow.com/questions/123198/how-do-i-copy-a-file
# update metadata
try:
# create metadata backup
shutil.copyfile(notebookMetadataPath,notebookMetadataPath + ".backup")
with open(notebookMetadataPath,"wt") as metadata:
metadata.write(json.dumps(notebookMatadata))
pass
except:
print("updateNotebookMatadata ERROR: Unable to create metadata backupfile or to update metadata.json of the notebook.")
return True
return False
バックエンドで一定期間ごとにタスクを実行できるように変更
multiprocessing と asyncio の組み合わせで、ただフロントエンドからの websocket の接続ややり取りを待ち受けるだけでなく、一定時間ごとに、タスクを実行できるようにした。
例えば、特定のノートブックのメタデータから、ページの参照が一定期間削除されたままになっているページを完全に削除するタスクなどが考えられる。
- main.py
# main server scirpt
# serve dataserver function with websocket.
import asyncio
import multiprocessing
# from modules.reqestParser import parser
from websockets.asyncio.server import serve
from helper import loadSettings
from helper.netwrok import receiveLoop
import tasks
import controller
async def mainLoop(websocket):
await receiveLoop(websocket,controller.controller)
async def dataserver():
async with serve(mainLoop, loadSettings.settings["ip"], loadSettings.settings["port"]) as server:
await server.serve_forever()
def dataserverThread():
asyncio.run(dataserver())
def serviceThread():
asyncio.run(tasks.taskController(loadSettings.settings["taskInterval"]))
if __name__ == "__main__":
multiprocessing.Process(target=dataserverThread).start()
multiprocessing.Process(target=serviceThread).start()
バックエンドにおいてエラーレスポンスを返すためのヘルパー関数を実装
直接エラーレスポンスを返すコードをいちいち書いていると冗長になり、ミスが増えることがわかったので、共通で使える関数errorResponseを用意してみた。
# arg:
# websocket : the connection to the frontend via websocket
# request : the frontend reqiuest entire JSON data
# errorMessage : an error message to show in the stdout and response to the frontend
# variablesList : an error related vars list to show in the stdout and response to the frontend
# return value
# OK : None
# Error : Error state does not exist.
async def errorResponse(websocket,request:dict,errorMessage:str,variablesList:list):
print("{} ERROR: {}".format(request["command"],errorMessage))
variableState = {}
print("{} ERROR: variable state --------------------------".format(request["command"]))
for aVar in variablesList:
# print variable name and data
# https://stackoverflow.com/questions/18425225/getting-the-name-of-a-variable-as-a-string
# f'{aaa=}'.split("=")[0]
varName = f'{aVar=}'.split("=")[0]
print("{}:\n{}\n".format(varName,aVar))
variableState[varName] = str(aVar)
print("{} ERROR: variable state end --------------------------".format(request["command"]))
responseString = json.dumps({
"status" : "error",
"errorMessage" : errorMessage,
"UUID" : request["UUID"],
"command" : request["command"],
"data" : variableState
})
await websocket.send(responseString)
print(">>> " + responseString)
躓いたこと
状態変数 と ReactElement
条件判定で挙動を変えたい UI (pageTypeList) があり、部分的に変数として切り出していたところ、適切にその UI 自体を更新しないと、正しく他の部分と提携して動作しないことがわかった。
どうも、pageTypeList を useEffect 内で生成したときに、onChange に設定したコールバック関数がコピーされていて、かつコールバック関数の中身にある 状態変数の値なども、参照ではなくコピーされていることで、newPageInfo の古い値が pageTypeList ごと更新してやらないと残り続けてしまうようである。
問題のある状態
export function deletepage(){
// 何かしらのコード...
// データサーバーから取得した情報が格納される状態変数
const [pageType,setPageType] = useState<pageType | null>(null)
// 正しく更新されないデータ
const [newPageInfo,setNewPageInfo] = useState({
"notebook": "",
"pagename": "",
"pageType": ""
})
// 何かしらのコード...
// 切り出した UI が格納される変数
const [pageTypeList,setPageTypeList] = useState<ReactElement[]>()
// 何かしらのコード...
// newPageInfo 状態変数が変更されても更新されない
useEffect(() => {
console.log(pageType)
const optionStyle = "bg-gray-800 hover:bg-gray-700"
const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
console.log("type selected")
console.log(newPageInfo)
setNewPageInfo({
notebook: newPageInfo.notebook, // newPageInfo の pageType が更新されても、それ以外は古い情報が反映され続ける
pagename: newPageInfo.pagename, // newPageInfo の pageType が更新されても、それ以外は古い情報が反映され続ける
pageType: event.target.value
})
}
// こうしても駄目だった。
// const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
// console.log("type selected")
// console.log(newPageInfo)
// setNewPageInfo({
// ...newPageInfo, // newPageInfo の pageType が更新されても、それ以外は古い情報が反映され続ける
// pageType: event.target.value
// })
// }
if(pageType == null){
setPageTypeList([
<select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
<option className={optionStyle} value="currently">No value exist.</option>
</select>])
}else if(pageType["data"] == null){
setPageTypeList([
<select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
<option className={optionStyle} value="currently">Currently, no value exist.</option>
</select>])
}else{
setPageTypeList([
<select onChange={typeSelected} key="ValueExist" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
{pageType.data.map((value,index) => <option className={optionStyle} value={value} key={index}>{value}</option>)}
</select>])
}
},[pageType])
// newPageInfo 状態変数が変更されても更新されない
// 何かしらのコード...
// return 内部では、newPageInfo 状態変数の内容は最新の内容が参照され、問題が起こらなかった。逆に言えば、UI を個別に変数として切り分けるまで問題に気付かなかった。
return <OverlayWindow arg={args}>
<div className="m-[1rem] flex flex-col">
<div className="item pagename flex">
<div className="label">Name:</div>
<input
className="ml-[1rem] border-gray-700 soild border-[2px]"
id="newPageName"
type="text"
onChange={(event:ChangeEvent<HTMLInputElement>) => {
setNewPageInfo({
...newPageInfo,
pagename:event.target.value})
}}></input>
</div>
<div className="item pageType flex mt-[0.5rem]">
<div className="label">Type: </div>
{pageTypeList}
</div>
<div className="item flex flex-col mt-[0.7rem]">
<div className="label mr-auto">Location: </div>
<div className="PathPreview border-l-4 border-l-gray-800 flex flex-col pl-[0.7rem] bg-gray-950">
<div className="notebook flex p-[0.5rem]">
<div className="mr-auto">Notebook: </div>
<div>{newPageInfo.notebook}</div>
</div>
<div className="page flex p-[0.5rem] pt-[0]">
<div className="mr-auto">Page: </div>
<div>{newPageInfo.pagename}</div>
</div>
</div>
</div>
<div className={submitButtonStyle}>Create New Page</div>
</div>
</OverlayWindow>
}
とりあえず動く状態
export function CreatePage(){
// 何かしらのコード...
// データサーバーから取得した情報が格納される状態変数
const [pageType,setPageType] = useState<pageType | null>(null)
// 正しく更新されないデータ
const [newPageInfo,setNewPageInfo] = useState({
"notebook": "",
"pagename": "",
"pageType": ""
})
// 何かしらのコード...
// 切り出した UI が格納される変数
const [pageTypeList,setPageTypeList] = useState<ReactElement[]>()
// 何かしらのコード...
// newPageInfo 状態変数が変更されたら、useEffect がかかり newPageInfo 状態変数の最新の内容が反映される
useEffect(() => {
console.log(pageType)
const optionStyle = "bg-gray-800 hover:bg-gray-700"
const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
console.log("type selected")
console.log(newPageInfo)
setNewPageInfo({
notebook: newPageInfo.notebook, // newPageInfo の pageType が更新されると、他の値は最新の newPageInfo の内容になる
pagename: newPageInfo.pagename, // newPageInfo の pageType が更新されると、他の値は最新の newPageInfo の内容になる
pageType: event.target.value
})
}
// const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
// console.log("type selected")
// console.log(newPageInfo)
// setNewPageInfo({
// ...newPageInfo, // newPageInfo の pageType が更新されると、他の値は最新の newPageInfo の内容になる
// pageType: event.target.value
// })
// }
if(pageType == null){
setPageTypeList([
<select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
<option className={optionStyle} value="currently">No value exist.</option>
</select>])
}else if(pageType["data"] == null){
setPageTypeList([
<select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
<option className={optionStyle} value="currently">Currently, no value exist.</option>
</select>])
}else{
setPageTypeList([
<select onChange={typeSelected} key="ValueExist" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
{pageType.data.map((value,index) => <option className={optionStyle} value={value} key={index}>{value}</option>)}
</select>])
}
},[pageType,newPageInfo]) // newPageInfo 変更時に、pageTypeList ごと更新をかける
// newPageInfo 状態変数が変更されたら、useEffect がかかり newPageInfo 状態変数の最新の内容が反映される
// 何かしらのコード...
// return 内部では、newPageInfo 状態変数の内容は最新の内容が参照され、問題が起こらなかった。逆に言えば、UI を個別に変数として切り分けるまで問題に気付かなかった。
return <OverlayWindow arg={args}>
<div className="m-[1rem] flex flex-col">
<div className="item pagename flex">
<div className="label">Name:</div>
<input
className="ml-[1rem] border-gray-700 soild border-[2px]"
id="newPageName"
type="text"
onChange={(event:ChangeEvent<HTMLInputElement>) => {
setNewPageInfo({
...newPageInfo,
pagename:event.target.value})
}}></input>
</div>
<div className="item pageType flex mt-[0.5rem]">
<div className="label">Type: </div>
{pageTypeList}
</div>
<div className="item flex flex-col mt-[0.7rem]">
<div className="label mr-auto">Location: </div>
<div className="PathPreview border-l-4 border-l-gray-800 flex flex-col pl-[0.7rem] bg-gray-950">
<div className="notebook flex p-[0.5rem]">
<div className="mr-auto">Notebook: </div>
<div>{newPageInfo.notebook}</div>
</div>
<div className="page flex p-[0.5rem] pt-[0]">
<div className="mr-auto">Page: </div>
<div>{newPageInfo.pagename}</div>
</div>
</div>
</div>
<div className={submitButtonStyle}>Create New Page</div>
</div>
</OverlayWindow>
}
Python 再帰の限界
Python では、再帰できる回数に制限があるようで、それでコケるということがあった。
問題のある状態
- 問題の再帰コード
async def taskController(interval:int):
if(interval == 0 or interval < 1):
print("taskController: The interval setting is too short.: {} sec".format(interval))
print("taskController: Please make it longer.")
interval = 1
for aJob in jobs:
aJob()
await asyncio.sleep(interval)
await taskController(interval)
- 限界に達すると出るエラー
Process Process-2:
Traceback (most recent call last):
File "/usr/lib/python3.13/multiprocessing/process.py", line 313, in _bootstrap
self.run()
~~~~~~~~^^
File "/usr/lib/python3.13/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/main.py", line 25, in serviceThread
asyncio.run(tasks.taskController(loadSettings.settings["taskInterval"]))
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/asyncio/runners.py", line 195, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "/usr/lib/python3.13/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/usr/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 24, in taskController
await taskController(interval)
File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 24, in taskController
await taskController(interval)
File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 24, in taskController
await taskController(interval)
[Previous line repeated 975 more times]
File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 23, in taskController
await asyncio.sleep(interval)
File "/usr/lib/python3.13/asyncio/tasks.py", line 714, in sleep
h = loop.call_later(delay,
futures._set_result_unless_cancelled,
future, result)
File "/usr/lib/python3.13/asyncio/base_events.py", line 799, in call_later
timer = self.call_at(self.time() + delay, callback, *args,
context=context)
File "/usr/lib/python3.13/asyncio/base_events.py", line 816, in call_at
timer = events.TimerHandle(when, callback, args, self, context)
File "/usr/lib/python3.13/asyncio/events.py", line 114, in __init__
super().__init__(callback, args, loop, context)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/asyncio/events.py", line 45, in __init__
if self._loop.get_debug():
~~~~~~~~~~~~~~~~~~~~^^
RecursionError: maximum recursion depth exceeded
とりあえず解決とした状態
再帰しすぎるのが駄目ならということで、単純に無限ループするようにしてみた。
async def taskController(interval:int):
while(True):
if(interval == 0 or interval < 1):
print("taskController: The interval setting is too short.: {} sec".format(interval))
print("taskController: Please make it longer.")
interval = 1
for aJob in jobs:
aJob()
await asyncio.sleep(interval)
os.system() と platform.system()
helper の中にある deleteDataSafely 関数において、プラットフォームを識別するために、情報を取得する関数を間違えていた。紛らわしいので、一応記録する。
os.system(command:str)
大雑把には、指定されたコマンドを実行する関数。
platform.system()
大まかにどの OS を動かしているか情報を得る。
- ‘Windows’
- ‘Linux’
- ‘Darwin’ (mac os)
- ‘Java’
- ’’ <- 空文字 (識別不能のとき)
Windows と文字コード
私は今回のプログラムを作っている中で当初は、Python 3 以降を使えば必然的に全部 UTF-8 でやり取りされるだろうと思い込んでいた。
しかし、実際に linux 上で開発していたものを windows 上に持ち込むと、少なくとも anaconda 経由でインストールされた Python 3.13.2 では ShiftJIS がデフォルトになっていて、linux 上で開発していた時と文字コードが合わなくなる問題が起こった。
結果として起こったことが、テストデータに日本語が含まれていると、UTF-8 で保存されたファイルを、ShiftJIS で読み込もうとして、以下のようなエラー(illegal multibyte sequence)となり、ページデータの内容を読み込めないということになった。ちなみにこの時、英語だけで構成されたデータは問題なく読み込めてしまう。
----------------------
<<<{"command":"pageInfo","UUID":"25eca682-e30b-4562-8602-90891238c5a5","data":{"pageID":"test.json","notebook":"test"}}
connection handler failed
Traceback (most recent call last):
File "C:\Users\username\miniconda3\Lib\site-packages\websockets\asyncio\server.py", line 376, in conn_handler
await self.handler(connection)
File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\main.py", line 15, in mainLoop
await receiveLoop(websocket,controller.controller)
File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\helper\netwrok.py", line 24, in receiveLoop
await callback(message,websocket)
File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\controller.py", line 54, in controller
await commands[requestedCommand](request,websocket)
File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\commands\notebookAndPage\pageInfo.py", line 35, in pageInfo
contentString = aPage.read()
UnicodeDecodeError: 'cp932' codec can't decode byte 0x86 in position 430: illegal multibyte sequence
実際の対応
以下のように全部ファイルPython built-in の open関数でを操作する際、全部文字コードを ”UTF-8” で統一した。
open(pagePath,"wt",encoding="utf-8")
Python ドキュメント
ChatGPT にただ質問を投げて受け取ったのを真に受けるのは良くないので、どうしてそうなるのか公式ドキュメントを漁ってみたところ、なぜ Windows 環境で動いている Python 3 において、ShiftJIS を使おうとするか、見つかった。
Built-in Functions — Python 3.13.7 documentationには次のような記述がある。
mode is an optional string that specifies the mode in which the file is opened. It defaults to ‘r’ which means open for reading in text mode. Other common values are ‘w’ for writing (truncating the file if it already exists), ‘x’ for exclusive creation, and ‘a’ for appending (which on some Unix systems, means that all writes append to the end of the file regardless of the current seek position). In text mode, if encoding is not specified the encoding used is platform-dependent: locale.getencoding() is called to get the current locale encoding. (For reading and writing raw bytes use binary mode and leave encoding unspecified.) The available modes are:
特に重要そうだと思った部分は以下のところである。
if encoding is not specified the encoding used is platform-dependent: locale.getencoding() is called to get the current locale encoding.
そこで、実際に Windows 環境で、Anaconda 経由でインストールされた、Python 3.13.2 を使って、locale.getencoding() をしてみると以下のような結果が得られた。
(base) PS C:\Users\username\work\OneNoteAlternative> python
Python 3.13.2 | packaged by Anaconda, Inc. | (main, Feb 6 2025, 18:49:14) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> import locale
>>> locale.getencoding()
'cp932'
>>>
また、Linux 環境において、同じことを試すと以下のようになった。
# python
Python 3.13.5 | packaged by Anaconda, Inc. | (main, Jun 12 2025, 16:09:02) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> import locale
>>> locale.getencoding()
'UTF-8'
>>>
よって、少なくとも linux と Windows では、少なくとも私が持っている環境においては、open関数が参照しているlocale.getencoding()の返す値が異なり、異なる文字コード(ShiftJIS,UTF8)がファイルの読み書きにデフォルトで使われることが分かった。
ただ気になる点としては、sys.getdefaultencoding()においては、UTF8 が linux と Windows では返ってきており、なぜlocale.getencoding()と挙動が違うのかはまだ分かっていない。
確かに OS 事情を考慮すると、DOS 時代あたりから ShiftJIS が Windows では採用されていて、広く使われた結果、いまUTF8が幅を利かせてきても、まだ互換性の呪いが残っているのかな?みたいに勝手なことを思ったりはした。それに対して linux 環境は普通のデスクトップ環境としてはあまり広く使われていなかったことから、UTF8 が早い段階で事実上の標準になっていたのかななんて思ったりもした。
- Built-in Functions — Python 3.13.7 documentation
https://docs.python.org/3/library/functions.html#open (2025年10月7日) - locale — Internationalization services — Python 3.13.7 documentation
https://docs.python.org/3/library/locale.html#locale.getencoding (2025年10月7日) - Pythonの日本語処理
https://web.wakayama-u.ac.jp/~kazama/lab/python/i18n.html (2025年10月7日)
ChatGPT の痕跡
Record を使う提案
ShiftJIS の件
以下はwindows-と文字コードに関連のあるものである。
参考にしたサイトとか
-
HTML contenteditable global attribute - HTML | MDN
https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable (2025年10月3日) -
VirtualKeyboard API - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/VirtualKeyboard_API (2025年10月3日) -
highlight.js
https://highlightjs.org/ (2025年10月3日) -
Using highlight.js in React
https://blog.robbie.digital/posts/highlight-js (2025年10月4日) -
javascript - contenteditable change events - Stack Overflow
https://stackoverflow.com/questions/1391278/contenteditable-change-events (2025年10月4日) -
javascript - Why does React warn against an contentEditable component having children managed by React? - Stack Overflow
https://stackoverflow.com/questions/49639144/why-does-react-warn-against-an-contenteditable-component-having-children-managed (2025年10月4日) -
useEffect – React
https://react.dev/reference/react/useEffect (2025年10月4日) -
python - How do I copy a file? - Stack Overflow
https://stackoverflow.com/questions/123198/how-do-i-copy-a-file (2025年10月4日) -
cross platform - How can I find the current OS in Python? - Stack Overflow
https://stackoverflow.com/questions/110362/how-can-i-find-the-current-os-in-python (2025年10月5日) -
os — Miscellaneous operating system interfaces — Python 3.13.7 documentation
https://docs.python.org/3/library/os.html#os.system (2025年10月5日) -
platform — Access to underlying platform’s identifying data — Python 3.13.7 documentation
https://docs.python.org/3/library/platform.html#platform.system (2025年10月5日) -
html - Scroll only showing part of the element - Stack Overflow
https://stackoverflow.com/questions/62514305/scroll-only-showing-part-of-the-element (2025年10月5日) -
python - Getting the name of a variable as a string - Stack Overflow
https://stackoverflow.com/questions/18425225/getting-the-name-of-a-variable-as-a-string (2025年10月6日) -
ChatGPT
https://chatgpt.com/ (2025年10月6日) -
Built-in Functions — Python 3.13.7 documentation
https://docs.python.org/3/library/functions.html#open (2025年10月7日) -
locale — Internationalization services — Python 3.13.7 documentation
https://docs.python.org/3/library/locale.html#locale.getencoding (2025年10月7日) -
Pythonの日本語処理
https://web.wakayama-u.ac.jp/~kazama/lab/python/i18n.html (2025年10月7日)