mesdan 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/YAPMALI.md +18 -0
- package/package.json +5 -1
- package/sevk/ihrac/ek--osmani-mesdan.js +16 -13
- package/sevk/ihrac/use-mesdan.js +2 -1
- package/sevk/nuve/kanca/ekle-osmani-metne-harf.js +17 -15
- package/sevk/nuve/kanca/harf-latiniden-osmaniye-eski.js +5 -0
- package/sevk/nuve/lexical/harf-tebdilleri.js +2 -1
package/YAPMALI.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
### Suggestions for Further Improvements
|
|
2
|
+
|
|
3
|
+
This library is already forming a robust baseline, but there are some excellent features you could add to make it an essential tool for Ottoman Turkish typing and education:
|
|
4
|
+
|
|
5
|
+
**1. On-Screen Virtual Keyboard (Visual Map)**
|
|
6
|
+
Since the mapping is custom (e.g., `A` -> `ع` with shift, `a` -> `ا`), a visual toggleable on-screen keyboard component mapping out these keys would be incredibly useful. Teaching users the layout is usually the hardest part of custom input methods. You could even create an interactive UI where pressing `Shift` or `Alt` on the physical keyboard updates the characters shown on the virtual map in real time.
|
|
7
|
+
|
|
8
|
+
**2. Ottoman Auto-Complete & Spell Check (Luğat/Dictionary Integration)**
|
|
9
|
+
Unlike modern Turkish, Ottoman Turkish is heavily historical and non-phonetic in its spelling (e.g., writing "olmak" as `اولمق`, or "gök" as `كوك`). Users often misspell words by trying to write them exactly as they sound today. Building an autocomplete dictionary overlay inside the Lexical plugin (suggesting the correct historical spelling from a Kamus-ı Türki dataset when a user types a Latin word) would be a killer feature.
|
|
10
|
+
|
|
11
|
+
**3. Batch Transliteration Tool**
|
|
12
|
+
Currently, users type in real-time. Adding a utility function (or a button in the UI) to "Paste Latin Text -> Output Ottoman Text" in bulk would be great. You already have `tebdilMetniOsmaniye`, but wrapping it in an easy-to-use batch conversion UI feature would attract users who have large texts to transcribe.
|
|
13
|
+
|
|
14
|
+
**4. Visible Zero-Width Non-Joiner (ZWNJ / Kesek) Toggling**
|
|
15
|
+
You use `\u200C` (mapped to `w`) to prevent letters from joining. In a rich text editor like Lexical, `\u200C` is invisible, which can confuse users when they try to delete characters. You could write a Lexical Node modifier that visually highlights ZWNJ characters (perhaps as a tiny vertical dashed line) when the editor is in focus, making it easier for teachers to explain word separation.
|
|
16
|
+
|
|
17
|
+
**5. Ligature Control (Allah, Lam-Elif, etc.)**
|
|
18
|
+
Some Ottoman texts require strict control over whether `Lam` and `Elif` combine into `لا`, or if the word `Allah` automatically forms the beautiful `اللّٰه` ligature. Adding a toggle configuration for "Historical Ligatures" vs "Plain Text" would be a very advanced, professional-grade typographical feature.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mesdan",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "Latinî mesdan ile Osmanî metin yazmak için hazırlanmış bir alettir.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"osmanlıca",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@types/react": "^18.2.64 || ^19.0.8",
|
|
32
32
|
"@types/react-dom": "^18.2.19 || ^19.0.3",
|
|
33
33
|
"@vitejs/plugin-react": "^5.1.0",
|
|
34
|
+
"baseline-browser-mapping": "^2.10.9",
|
|
34
35
|
"jest": "^29.7.0",
|
|
35
36
|
"lexical": "^0.38.2",
|
|
36
37
|
"prettier": "^3.6.2",
|
|
@@ -54,5 +55,8 @@
|
|
|
54
55
|
"@types/react-dom": {
|
|
55
56
|
"optional": true
|
|
56
57
|
}
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"wrangler": "^4.76.0"
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
2
|
-
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND } from 'lexical';
|
|
2
|
+
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND } from 'lexical';
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
import { harfLatinidenOsmaniye } from '../nuve/lexical/harf-latiniden-osmaniye';
|
|
5
5
|
import { silOsmaniHarfiBaşra, silOsmaniHarfiSonra } from '../nuve/lexical/sil-harf-metingerde';
|
|
6
|
-
import { LATIN_HURUFATI_VE_RAKAMATI_REGEX } from '../nuve/sabit';
|
|
7
6
|
export const OsmaniMesdanEki = () => {
|
|
8
7
|
const [metinger] = useLexicalComposerContext();
|
|
9
8
|
useEffect(() => {
|
|
@@ -54,7 +53,8 @@ export const OsmaniMesdanEki = () => {
|
|
|
54
53
|
if (ca === 'ArrowUp' || ca === 'ArrowDown') {
|
|
55
54
|
return false;
|
|
56
55
|
}
|
|
57
|
-
|
|
56
|
+
// Bütün basılabilir karakterleri (harfler, rakamlar, noktalama işaretleri) yakala
|
|
57
|
+
if (ca.length === 1) {
|
|
58
58
|
vaqa.preventDefault();
|
|
59
59
|
vaqa.stopPropagation();
|
|
60
60
|
metinger.update(() => {
|
|
@@ -72,6 +72,19 @@ export const OsmaniMesdanEki = () => {
|
|
|
72
72
|
}
|
|
73
73
|
else {
|
|
74
74
|
const osmanliHarf = harfLatinidenOsmaniye(ca, marHarf, shiftKey, altKey);
|
|
75
|
+
// Eğer yazdırılacak harf sadece harekelerden/işaretlerden ibaretse
|
|
76
|
+
const isaretRegex = /^[\u064B-\u0656\u0670]+$/;
|
|
77
|
+
if ($isTextNode(node) && isaretRegex.test(osmanliHarf)) {
|
|
78
|
+
// Öncesinde de hareke/işaret var mı baq
|
|
79
|
+
const match = marMetin.match(/[\u064B-\u0656\u0670]+$/);
|
|
80
|
+
if (match) {
|
|
81
|
+
const matchLength = match[0].length;
|
|
82
|
+
// Önceki harekeleri seç ve sil
|
|
83
|
+
selection.anchor.set(node.getKey(), offset - matchLength, 'text');
|
|
84
|
+
selection.focus.set(node.getKey(), offset, 'text');
|
|
85
|
+
selection.removeText();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
75
88
|
selection.insertText(osmanliHarf);
|
|
76
89
|
}
|
|
77
90
|
});
|
|
@@ -79,18 +92,8 @@ export const OsmaniMesdanEki = () => {
|
|
|
79
92
|
}
|
|
80
93
|
return false;
|
|
81
94
|
}, COMMAND_PRIORITY_HIGH);
|
|
82
|
-
// --- Node transform: only handle pasted text ---
|
|
83
|
-
// const removeNodeTransform = editor.registerNodeTransform(TextNode, node => {
|
|
84
|
-
// const text = node.getTextContent();
|
|
85
|
-
// if (!LATIN_HURUFATI_VE_RAKAMATI_REGEX.test(text)) return;
|
|
86
|
-
// const transformed = tebdilMetniOsmaniye(text, { shiftKey: false, altKey: false });
|
|
87
|
-
// if (transformed !== text) {
|
|
88
|
-
// node.setTextContent(transformed);
|
|
89
|
-
// }
|
|
90
|
-
// });
|
|
91
95
|
return () => {
|
|
92
96
|
removeKeyDownListener();
|
|
93
|
-
// removeNodeTransform();
|
|
94
97
|
};
|
|
95
98
|
}, [metinger]);
|
|
96
99
|
return null;
|
package/sevk/ihrac/use-mesdan.js
CHANGED
|
@@ -59,7 +59,8 @@ export const useMesdan = ({ ibtidaiMetin = '', tekSatırMı, latiniMi: ibtidaiLa
|
|
|
59
59
|
tadiliMetinEmri = TadiliMetinEmri.ALEM_ÜSTE;
|
|
60
60
|
else if (hadise.key === 'ArrowDown')
|
|
61
61
|
tadiliMetinEmri = TadiliMetinEmri.ALEM_ALTA;
|
|
62
|
-
else if (hadise.ctrlKey) {
|
|
62
|
+
else if (hadise.ctrlKey && !hadise.altKey) {
|
|
63
|
+
// !hadise.altKey guarantees this doesn't block AltGr keys (which produce @, #, etc.)
|
|
63
64
|
if (hadise.key.toLowerCase() === 'c')
|
|
64
65
|
tadiliMetinEmri = TadiliMetinEmri.BELLE;
|
|
65
66
|
else if (hadise.key.toLowerCase() === 'x')
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import { gayriİşaretlerdenMi, harekeMi, kesekMi, tenvinMi, şeddeMi } from '../../aletler/harf-tedkiki-kume';
|
|
2
2
|
import { harfLatinidenOsmaniye } from '../lexical/harf-latiniden-osmaniye';
|
|
3
3
|
export function ekleOsmaniMetneHarf(mevcutMetin, alemMevkisi, mesHadisesi) {
|
|
4
|
-
// console.log("ekleOsmaniMetinyeHarf: >" + mesHadisesi.latiniHarf + "<");
|
|
5
4
|
let metin = mevcutMetin;
|
|
6
5
|
const niyetHarekeMi = mesHadisesi.capsLockBasılıMı;
|
|
7
6
|
if (metin.length < 1) {
|
|
8
7
|
metin = harfLatinidenOsmaniye(mesHadisesi.latiniHarf, '', mesHadisesi.shiftBasılıMı, mesHadisesi.altBasılıMı).replace('\u200C', '');
|
|
9
8
|
return { metinOsmani: metin, alemBaşMevkisi: metin.length };
|
|
10
9
|
}
|
|
11
|
-
|
|
10
|
+
let alemBaşrası = metin.slice(0, alemMevkisi.başMevki);
|
|
12
11
|
const alemSonrası = metin.slice(alemMevkisi.sonMevki);
|
|
13
|
-
|
|
14
|
-
const alemBaşrasındakiİkiHarf = alemBaşrası.slice(-2);
|
|
15
|
-
// const alemSonrasındekiHarf = alemSonrası.slice(0, 1);
|
|
16
|
-
// console.log("alemBaşrasındakiHarf: > " + alemBaşrasındakiHarf + " <");
|
|
17
|
-
// console.log("alemSonrasındekiHarf: > " + alemSonrasındekiHarf + " <");
|
|
12
|
+
let alemBaşrasındakiHarf = alemBaşrası.slice(-1);
|
|
18
13
|
const ilave = harfLatinidenOsmaniye(mesHadisesi.latiniHarf, alemBaşrasındakiHarf, mesHadisesi.shiftBasılıMı, mesHadisesi.altBasılıMı);
|
|
14
|
+
// --- Harekeleri/İşaretleri Değiştirme (Replacement) Mantığı ---
|
|
15
|
+
const isaretRegex = /^[\u064B-\u0656\u0670]+$/;
|
|
16
|
+
if (isaretRegex.test(ilave)) {
|
|
17
|
+
const match = alemBaşrası.match(/[\u064B-\u0656\u0670]+$/);
|
|
18
|
+
if (match) {
|
|
19
|
+
// Eğer öncesinde hareke varsa, silip yenisini koymak için alemBaşrası'nı kırpıyoruz.
|
|
20
|
+
alemBaşrası = alemBaşrası.slice(0, -match[0].length);
|
|
21
|
+
alemBaşrasındakiHarf = alemBaşrası.slice(-1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// --------------------------------------------------------------
|
|
25
|
+
const alemBaşrasındakiİkiHarf = alemBaşrası.slice(-2);
|
|
19
26
|
metin = alemBaşrası + ilave + alemSonrası;
|
|
20
27
|
let alemHareketMiktarı = ilave.length;
|
|
21
28
|
if (niyetHarekeMi) {
|
|
22
29
|
const alemBaşrasıHarekeMi = harekeMi(alemBaşrasındakiHarf);
|
|
23
|
-
// const ilaveHarekeMi = harekeMi(ilave);
|
|
24
30
|
const alemBaşrasıTenvinMi = tenvinMi(alemBaşrasındakiHarf);
|
|
25
|
-
// const ilaveTenvinMi = tenvinMi(ilave);
|
|
26
31
|
const alemBaşrasıGayriİşaretlerdenMi = gayriİşaretlerdenMi(alemBaşrasındakiHarf);
|
|
27
|
-
// const ilaveGayriİşaretlerdenMi = gayriİşaretlerdenMi(ilave);
|
|
28
32
|
const alemBaşrasıŞeddeMi = şeddeMi(alemBaşrasındakiİkiHarf);
|
|
29
33
|
const ilaveŞeddeMi = şeddeMi(ilave);
|
|
30
34
|
const alemBaşrasıKesmeMi = kesekMi(alemBaşrasındakiHarf);
|
|
@@ -40,7 +44,7 @@ export function ekleOsmaniMetneHarf(mevcutMetin, alemMevkisi, mesHadisesi) {
|
|
|
40
44
|
const kesekEvveliŞeddeMi = şeddeMi(kesekEvvelindekiİkiHarf);
|
|
41
45
|
if (alemBaşrasıKesmeMi) {
|
|
42
46
|
if (ilaveKesmeMi) {
|
|
43
|
-
metin =
|
|
47
|
+
metin = alemBaşrası + alemSonrası; // Mevcut metne dön (ilaveyi yoksay)
|
|
44
48
|
}
|
|
45
49
|
else {
|
|
46
50
|
if (kesekEvveliŞeddeMi) {
|
|
@@ -99,11 +103,9 @@ export function ekleOsmaniMetneHarf(mevcutMetin, alemMevkisi, mesHadisesi) {
|
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
else if (alemBaşrası.endsWith('ا') && ilave === 'ا') {
|
|
102
|
-
metin =
|
|
103
|
-
alemHareketMiktarı =
|
|
106
|
+
metin = alemBaşrası.slice(0, -1) + 'آ' + alemSonrası;
|
|
107
|
+
alemHareketMiktarı = 0;
|
|
104
108
|
}
|
|
105
|
-
// console.log("ilave.length: " + ilave.length);
|
|
106
|
-
// console.log("alemHareketMiktarı: " + alemHareketMiktarı);
|
|
107
109
|
return {
|
|
108
110
|
metinOsmani: metin,
|
|
109
111
|
alemBaşMevkisi: alemBaşrası.length + alemHareketMiktarı,
|
|
@@ -31,6 +31,8 @@ export function harfLatinidenOsmaniyeEski(latiniHarf, evvelkiHarf, shiftBasılı
|
|
|
31
31
|
return '`';
|
|
32
32
|
case '``':
|
|
33
33
|
return '``';
|
|
34
|
+
case '@':
|
|
35
|
+
return '@';
|
|
34
36
|
case '=':
|
|
35
37
|
return '=';
|
|
36
38
|
case '-':
|
|
@@ -192,6 +194,9 @@ export function harfLatinidenOsmaniyeEski(latiniHarf, evvelkiHarf, shiftBasılı
|
|
|
192
194
|
case 'K':
|
|
193
195
|
return 'ك';
|
|
194
196
|
case 'q':
|
|
197
|
+
if (altBasılıMı) {
|
|
198
|
+
return '\u25CC'; // Dotted Circle
|
|
199
|
+
}
|
|
195
200
|
return 'ق';
|
|
196
201
|
case 'Q':
|
|
197
202
|
if (harfKelimeBaşındaMı(evvelkiHarf)) {
|
|
@@ -60,7 +60,7 @@ export const harfTebdilleri = {
|
|
|
60
60
|
J: { asıl: 'ژ' },
|
|
61
61
|
k: { asıl: 'ك' },
|
|
62
62
|
K: { asıl: 'ك' },
|
|
63
|
-
q: { asıl: 'ق' },
|
|
63
|
+
q: { asıl: 'ق', alt: '\u25CC' /* Dotted Circle */ },
|
|
64
64
|
Q: {
|
|
65
65
|
asıl: { baş: 'ق', bel: '\u064E' /* Fetha */ },
|
|
66
66
|
},
|
|
@@ -156,6 +156,7 @@ export const sabitTebdiller = new Map([
|
|
|
156
156
|
['_', '_'],
|
|
157
157
|
['<', '<'],
|
|
158
158
|
['>', '>'],
|
|
159
|
+
['@', '@'],
|
|
159
160
|
['0', '٠'],
|
|
160
161
|
['1', '١'],
|
|
161
162
|
['2', '٢'],
|