Arabic keyboard with transliteration.
// ==UserScript==
// @name Clavier arabe
// @namespace http://tampermonkey.net/
// @version 8.0
// @description Arabic keyboard with transliteration.
// @author A9ARTAS
// @match *://*/*
// @match *://newtab/*
// @match about:newtab
// @match chrome://newtab/*
// @match edge://newtab/*
// @grant GM_registerMenuCommand
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const SK = 'aekb7';
function injectCSS() {
if (document.getElementById('akcss')) return;
const s = document.createElement('style');
s.id = 'akcss';
s.textContent = `
#aekb {
position: fixed !important;
z-index: 2147483647 !important;
width: 92% !important; max-width: 420px !important;
display: none; flex-direction: column !important;
background: #1c1c1e !important;
border-radius: 14px !important;
box-shadow: 0 12px 48px rgba(0,0,0,.5), 0 0 0 0.5px rgba(255,255,255,.05) !important;
font: 14px/1.5 -apple-system, BlinkMacSystemFont, sans-serif !important;
color: #fff !important;
user-select: none !important;
overflow: hidden !important;
font-family: -apple-system, BlinkMacSystemFont, sans-serif !important;
}
#aekb * { box-sizing: border-box !important; margin: 0 !important; padding: 0 !important; font-family: inherit !important; }
#aekb-bar {
display: flex !important; align-items: center !important; justify-content: flex-end !important;
height: 32px !important; padding: 0 8px !important;
cursor: grab !important;
background: #1c1c1e !important;
}
#aekb-bar:active { cursor: grabbing !important; }
#aekb-x {
width: 26px !important; height: 26px !important; border-radius: 50% !important;
background: #3a3a3c !important; border: none !important; color: #8e8e93 !important;
font: 600 14px/1 -apple-system, sans-serif !important;
display: flex !important; align-items: center !important; justify-content: center !important;
cursor: pointer !important;
transition: background .12s, color .12s !important;
}
#aekb-x:hover { background: #48484a !important; color: #fff !important; }
#aekb-x:active { background: #5a5a5e !important; }
#aekb-inp {
width: 100% !important; padding: 12px 16px !important;
background: #2c2c2e !important; color: #fff !important;
border: none !important; outline: none !important;
font: 16px/1.4 -apple-system, sans-serif !important;
direction: rtl !important; resize: none !important; height: 50px !important;
}
#aekb-inp::placeholder { color: #8e8e93 !important; }
#aekb-cb {
display: none !important;
}
#aekb-copy {
background: #3a3a3c !important; border: none !important; border-radius: 6px !important;
color: #fff !important; font: 500 12px/1 -apple-system, sans-serif !important;
padding: 5px 16px !important; cursor: pointer !important;
transition: background .12s !important;
}
#aekb-copy:hover { background: #48484a !important; }
#aekb-g {
display: grid !important; grid-template-columns: repeat(10, 1fr) !important;
gap: 6px !important; padding: 6px 5px 4px !important;
background: #1c1c1e !important;
}
.aekb-k {
background: #3a3a3c !important; border: none !important; border-radius: 5px !important;
color: #fff !important; font: 400 18px/1 -apple-system, sans-serif !important;
display: flex !important; align-items: center !important; justify-content: center !important;
height: 42px !important; cursor: pointer !important;
transition: background .06s, transform .06s !important;
-webkit-tap-highlight-color: transparent !important;
padding: 0 !important;
box-shadow: 0 1px 0 rgba(0,0,0,.3) !important;
}
.aekb-k:hover { background: #48484a !important; }
.aekb-k:active { background: #545458 !important; transform: scale(.95) !important; }
.aekb-k.sp {
grid-column: span 4 !important; font-size: 13px !important; font-weight: 500 !important;
background: #636366 !important;
}
.aekb-k.sp:hover { background: #6e6e73 !important; }
.aekb-k.sp:active { background: #76767a !important; }
.aekb-k.cp {
grid-column: span 2 !important; font-size: 12px !important; font-weight: 500 !important;
background: #3a3a3c !important;
}
.aekb-k.cp:hover { background: #48484a !important; }
.aekb-k.cp:active { background: #545458 !important; }
#aekb-bot {
display: flex !important; justify-content: center !important; padding: 6px 0 8px !important;
background: #1c1c1e !important;
}
#aekb-bot div {
width: 36px !important; height: 4px !important; border-radius: 2px !important;
background: #48484a !important;
}
#akn {
position: fixed !important; bottom: 80px !important; left: 50% !important;
transform: translateX(-50%) !important;
background: rgba(30,30,30,.92) !important; color: #fff !important;
padding: 8px 20px !important; border-radius: 20px !important;
z-index: 2147483647 !important;
font: 500 13px/1.4 -apple-system, sans-serif !important;
pointer-events: none !important; white-space: nowrap !important;
animation: akf 1s ease forwards !important;
}
@keyframes akf {
0% { opacity: 1; transform: translateX(-50%) translateY(0) }
100% { opacity: 0; transform: translateX(-50%) translateY(-6px) }
}
`;
document.head.appendChild(s);
}
// ─── Transliteration ─────────────────────────────────────────────────
const R = [
[/==a/g,'\u064B'],[/==i/g,'\u064D'],[/==u/g,'\u064C'],
[/=a/g,'\u064E'],[/=i/g,'\u0650'],[/=u/g,'\u064F'],
[/=w/g,'\u0651'],[/=o/g,'\u0652'],
[/aa/g,'\u0622'],
[/[a\u00E2\u00E0\u0101]/g,'\u0627'],
[/b/g,'\u0628'],[/p/g,'\u067E'],[/t/g,'\u062A'],
[/\u1E6F/g,'\u062B'],[/[j\u01E7]/g,'\u062C'],[/c/g,'\u0686'],
[/[H\u1E25\u1E24]/g,'\u062D'],[/[x\u1E96K]/g,'\u062E'],
[/d/g,'\u062F'],[/\u1E0F/g,'\u0630'],[/r/g,'\u0631'],[/z/g,'\u0632'],
[/s/g,'\u0633'],[/\u0161/g,'\u0634'],[/[S\u1E63]/g,'\u0635'],
[/[D\u1E0D]/g,'\u0636'],[/[T\u1E6B]/g,'\u0637'],[/[Z\u1E93]/g,'\u0638'],
[/[g\u02BF]/g,'\u0639'],[/\u0121/g,'\u063A'],[/f/g,'\u0641'],[/v/g,'\u06A4'],
[/q/g,'\u0642'],[/k/g,'\u0643'],[/l/g,'\u0644'],[/m/g,'\u0645'],
[/n/g,'\u0646'],[/h/g,'\u0647'],
[/[wou\u00F4\u00FB\u014D\u016B]/g,'\u0648'],
[/[yie\u00EE\u012B]/g,'\u064A'],[/[YIE]/g,'\u0649'],[/\u1E97/g,'\u0629'],
[/[\-]/g,'\u0621'],[/\u02BE/g,'\u0621'],[/_/g,'\u0640'],
[/[?]/g,'\u061F'],[/[;]/g,'\u061B'],[/[,]/g,'\u060C'],
[/0/g,'\u0660'],[/1/g,'\u0661'],[/2/g,'\u0662'],[/3/g,'\u0663'],
[/4/g,'\u0664'],[/5/g,'\u0665'],[/6/g,'\u0666'],[/7/g,'\u0667'],
[/8/g,'\u0668'],[/9/g,'\u0669'],[/%/g,'\u066A'],
];
const POST = [
[/'\u0628/g,'\u067E'],[/\u0628'/g,'\u067E'],
[/'\u062A/g,'\u062B'],[/\u062A'/g,'\u062B'],
[/'\u062C/g,'\u0686'],[/\u062C'/g,'\u0686'],
[/'\u062D/g,'\u062E'],[/\u062D'/g,'\u062E'],
[/'\u062F/g,'\u0630'],[/\u062F'/g,'\u0630'],
[/'\u0631/g,'\u0632'],[/\u0631'/g,'\u0632'],
[/'\u0633/g,'\u0634'],[/\u0633'/g,'\u0634'],
[/'\u0635/g,'\u0636'],[/\u0635'/g,'\u0636'],
[/'\u0637/g,'\u0638'],[/\u0637'/g,'\u0638'],
[/'\u0639/g,'\u063A'],[/\u0639'/g,'\u063A'],
[/'\u0641/g,'\u06A4'],[/\u0641'/g,'\u06A4'],
[/'\u0642/g,'\u06A8'],[/\u0642'/g,'\u06A8'],
[/'\u0643/g,'\u06AD'],[/\u0643'/g,'\u06AD'],
[/'\u0647/g,'\u0629'],[/\u0647'/g,'\u0629'],
[/'\u0648/g,'\u0624'],[/\u0648'/g,'\u0624'],
[/'\u064A/g,'\u0649'],[/\u064A'/g,'\u0649'],
[/'\u0649/g,'\u0626'],[/\u0649'/g,'\u0626'],
[/'\u0627/g,'\u0623'],
[/\u0627\u0627/g,'\u0622'],
[/\u0648\u0621/g,'\u0624'],[/\u064A\u0621/g,'\u0626'],
[/\u0627\u0621/g,'\u0625'],[/\u0647\u0621/g,'\u0629'],
[/\u0621\u0627/g,'\u0623'],
];
function tfm(t) {
let r = t;
for (const [re, s] of R) r = r.replace(re, s);
for (const [re, s] of POST) r = r.replace(re, s);
return r;
}
// ─── State ───────────────────────────────────────────────────────────
let isOn = localStorage.getItem(SK) === 'true';
let dragging = false, ox = 0, oy = 0;
// ─── Input ───────────────────────────────────────────────────────────
function onInput(e) {
if (e.target.id !== 'aekb-inp') return;
const el = e.target;
const s = el.selectionStart, end = el.selectionEnd, st = el.scrollTop, o = el.value;
if (s === 0 && end === o.length) {
el.value = tfm(o);
el.selectionStart = 0; el.selectionEnd = el.value.length;
} else {
el.value = tfm(o);
el.selectionStart = el.selectionEnd = tfm(o.slice(0, s)).length;
}
el.scrollTop = st;
}
function ins(c) {
const e = document.getElementById('aekb-inp'); if (!e) return;
e.focus();
const s = e.selectionStart;
e.value = e.value.slice(0, s) + c + e.value.slice(e.selectionEnd);
e.selectionStart = e.selectionEnd = s + c.length;
e.dispatchEvent(new Event('input', { bubbles: true }));
}
function onKey(k) {
if (k === 'Space') return ins(' ');
ins(k);
}
// ─── Copy ────────────────────────────────────────────────────────────
async function doCopy() {
const e = document.getElementById('aekb-inp'); if (!e || !e.value) return;
try { await navigator.clipboard.writeText(e.value); }
catch { e.select(); document.execCommand('copy'); }
notify('Copied!');
}
// ─── Notify ──────────────────────────────────────────────────────────
function notify(msg) {
let n = document.getElementById('akn');
if (!n) { n = document.createElement('div'); n.id = 'akn'; document.body.appendChild(n); }
n.textContent = msg;
n.style.animation = 'none'; void n.offsetWidth;
n.style.animation = 'akf 1s ease forwards';
}
// ─── Layout (no del button) ──────────────────────────────────────────
const rows = [
['\u0636','\u0635','\u062B','\u0642','\u0641','\u063A','\u0639','\u0647','\u062E','\u062D'],
['\u0634','\u0633','\u064A','\u0628','\u0644','\u0627','\u062A','\u0646','\u0645','\u0643'],
['\u0626','\u0621','\u0624','\u0631','\u0644\u0627','\u0649','\u0629','\u0648','\u0632','\u0638'],
['\u0630','\u062F','\u062C','Space','\u0637','copy'],
];
// ─── Build ───────────────────────────────────────────────────────────
function build() {
injectCSS();
const p = document.createElement('div');
p.id = 'aekb';
// Title bar (drag handle)
const bar = document.createElement('div');
bar.id = 'aekb-bar';
const xb = document.createElement('button');
xb.id = 'aekb-x';
xb.innerHTML = '×';
xb.addEventListener('click', function() {
p.style.display = 'none';
isOn = false;
localStorage.setItem(SK, 'false');
});
bar.appendChild(xb);
p.appendChild(bar);
// Textarea
const ta = document.createElement('textarea');
ta.id = 'aekb-inp';
ta.placeholder = 'Type... \u0627\u0643\u062A\u0628...';
ta.addEventListener('input', onInput);
p.appendChild(ta);
// Copy bar
const cb = document.createElement('div');
cb.id = 'aekb-cb';
const cpb = document.createElement('button');
cpb.id = 'aekb-copy';
cpb.textContent = '\uD83D\uDCCB Copy';
cpb.addEventListener('click', doCopy);
cb.appendChild(cpb);
p.appendChild(cb);
// Keys
const g = document.createElement('div');
g.id = 'aekb-g';
for (const row of rows) {
for (const k of row) {
const b = document.createElement('button');
b.className = 'aekb-k';
if (k === 'Space') b.classList.add('sp');
if (k === 'copy') { b.classList.add('cp'); b.textContent = '\uD83D\uDCCB Copy'; b.addEventListener('click', doCopy); }
else { b.textContent = k; b.addEventListener('click', (function(key) { return function() { onKey(key); }; })(k)); }
g.appendChild(b);
}
}
p.appendChild(g);
// Bottom indicator
const bot = document.createElement('div');
bot.id = 'aekb-bot';
bot.appendChild(document.createElement('div'));
p.appendChild(bot);
document.body.appendChild(p);
// Drag
bar.addEventListener('mousedown', function(e) {
dragging = true;
const r = p.getBoundingClientRect();
ox = e.clientX - r.left;
oy = e.clientY - r.top;
p.style.transition = 'none';
e.preventDefault();
});
}
// ─── Global drag ─────────────────────────────────────────────────────
document.addEventListener('mousemove', function(e) {
if (!dragging) return;
const p = document.getElementById('aekb'); if (!p) return;
let x = e.clientX - ox, y = e.clientY - oy;
const w = window.innerWidth, h = window.innerHeight;
x = Math.max(0, Math.min(x, w - p.offsetWidth));
y = Math.max(0, Math.min(y, h - p.offsetHeight));
p.style.left = x + 'px';
p.style.top = y + 'px';
});
document.addEventListener('mouseup', function() { dragging = false; });
// ─── Toggle ──────────────────────────────────────────────────────────
function toggle() {
let p = document.getElementById('aekb');
if (!p) { build(); p = document.getElementById('aekb'); }
if (p.style.display === 'flex') {
p.style.display = 'none';
isOn = false;
} else {
p.style.left = Math.max(0, (window.innerWidth - p.offsetWidth) / 2) + 'px';
p.style.top = Math.max(0, (window.innerHeight - p.offsetHeight) / 2) + 'px';
p.style.display = 'flex';
isOn = true;
const t = document.getElementById('aekb-inp');
if (t) setTimeout(function() { t.focus(); }, 80);
}
localStorage.setItem(SK, isOn ? 'true' : 'false');
}
// ─── Init ────────────────────────────────────────────────────────────
function init() {
injectCSS();
try { GM_registerMenuCommand('Arabic keyboard', toggle); } catch (_) {}
if (isOn) toggle();
}
if (document.readyState === 'loading')
document.addEventListener('DOMContentLoaded', init);
else
init();
})();