YTypingの変換ありタイピングにYouTube Liveチャットから参加できる拡張機能
สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/576193/1813219/YTyping%20IME%20Mode%20Toggle.js
// ==UserScript==
// @name YTyping IME Mode Toggle
// @namespace https://ytyping.net/
// @version 1.0.1
// @description YTypingの変換ありタイピングにYouTube Liveチャットから参加できる拡張機能
// @author toshi7878
// @match https://ytyping.net/*
// @grant none
// ==/UserScript==
(function () {
"use strict";
// atomWithStorage(createJSONStorage(() => sessionStorage)) は値を JSON.stringify して保存する
// 例: sessionStorage["mapLinkMode"] === '"ime"' or '"type"'
const STORAGE_KEY = "mapLinkMode";
function getCurrentMode() {
try {
const raw = sessionStorage.getItem(STORAGE_KEY);
const parsed = JSON.parse(raw);
return parsed === "ime" ? "ime" : "type";
} catch {
return "type";
}
}
function applyMode(mode) {
if (typeof __ytyping !== "undefined" && typeof __ytyping.setMapLinkMode === "function") {
__ytyping.setMapLinkMode(mode);
}
}
function createToggle() {
const existingToggle = document.getElementById("yt-ime-toggle-wrapper");
if (existingToggle) return;
const navIcons = document.getElementById("right-nav-icons");
if (!navIcons) return;
const currentMode = getCurrentMode();
// wrapper
const wrapper = document.createElement("div");
wrapper.id = "yt-ime-toggle-wrapper";
wrapper.style.cssText = `
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0 4px;
`;
// label
const label = document.createElement("span");
label.id = "yt-ime-toggle-label";
label.textContent = currentMode === "ime" ? "IME" : "TYPE";
label.style.cssText = `
font-size: 11px;
font-weight: 600;
color: var(--color-text-secondary, #aaa);
letter-spacing: 0.05em;
min-width: 32px;
text-align: center;
user-select: none;
`;
// switch track
const switchLabel = document.createElement("label");
switchLabel.style.cssText = `
position: relative;
display: inline-block;
width: 36px;
height: 20px;
cursor: pointer;
flex-shrink: 0;
`;
const input = document.createElement("input");
input.type = "checkbox";
input.checked = currentMode === "ime";
input.style.cssText = `
opacity: 0;
width: 0;
height: 0;
position: absolute;
`;
const track = document.createElement("span");
track.style.cssText = `
position: absolute;
inset: 0;
border-radius: 20px;
background: ${currentMode === "ime" ? "var(--color-primary, #3b82f6)" : "var(--color-border, #555)"};
transition: background 0.2s ease;
`;
const thumb = document.createElement("span");
thumb.style.cssText = `
position: absolute;
top: 2px;
left: ${currentMode === "ime" ? "18px" : "2px"};
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
transition: left 0.2s ease;
`;
input.addEventListener("change", () => {
const newMode = input.checked ? "ime" : "type";
applyMode(newMode);
track.style.background = input.checked
? "var(--color-primary, #3b82f6)"
: "var(--color-border, #555)";
thumb.style.left = input.checked ? "18px" : "2px";
label.textContent = input.checked ? "IME" : "TYPE";
});
switchLabel.appendChild(input);
switchLabel.appendChild(track);
switchLabel.appendChild(thumb);
wrapper.appendChild(label);
wrapper.appendChild(switchLabel);
// #right-nav-icons の先頭に挿入
navIcons.insertBefore(wrapper, navIcons.firstChild);
// 初期適用
applyMode(currentMode);
}
// DOM が変化するたびに再試行(SPAナビゲーション対応)
const observer = new MutationObserver(() => {
if (document.getElementById("right-nav-icons")) {
createToggle();
}
});
observer.observe(document.body, { childList: true, subtree: true });
// 初回試行
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", createToggle);
} else {
createToggle();
}
})();