PageSourceSearch

https://harran.edu.tr/js/erisebilirlik.js?v=2

js harran.edu.tr collected 2026-09-24 20:02:51 UTC 14,814 bytes, 451 lines download raw bytes

1/* =========================================================
2   ERİŞİLEBİLİRLİK MENÜSÜ
3   Bağımsız çalışır — jQuery / jquery.cookie gerekmez.
4
5   Desteklenen kontroller:
6   1) Panel içindeki [data-toggle] ve [data-action] butonları
7   2) Sayfanın HERHANGİ bir yerindeki [data-toggle]/[data-action] butonları
8   3) Eski üst bar butonları: #Buyut #Kucult #Normal #Oku #Sus
9
10   Kullanım: <script src="/js/erisilebilirlik.js" defer></script>
11   ========================================================= */
12(function () {
13  "use strict";
14
15  var STORAGE_KEY = "a11y-ayarlar";
16  var root = document.documentElement;
17
18  /* group: aynı gruptaki modlardan yalnızca biri açık kalır */
19  var MODES = {
20    "high-contrast":    { group: "tema" },
21    "dark-mode":        { group: "tema" },
22    "light-mode":       { group: "tema" },
23    "grayscale":        { group: "filtre" },
24    "sepia":            { group: "filtre" },
25    "invert":           { group: "filtre" },
26    "highlight-links":  {},
27    "highlight-titles": {},
28    "dyslexia":         {},
29    "spacing":          {},
30    "big-cursor":       {},
31    "reading-guide":    {},
32    "reading-mask":     {},
33    "read-mode":        { special: true }   /* <html>'e sınıf eklenmez */
34  };
35
36  var MIN_SCALE = 80, MAX_SCALE = 200, STEP = 10;
37  var TEXT_SELECTOR =
38    "p,li,a,span,h1,h2,h3,h4,h5,h6,td,th,dt,dd,label,button,input,textarea," +
39    "select,figcaption,blockquote,small,strong,em,b,i";
40
41  var state = { modes: {}, scale: 100 };
42  var panel, toggleBtn, sizeLabel;
43  var guideEl, maskTop, maskBottom;
44  var lastSpoken = null;
45  var pointerY = null;
46
47  /* ===================== Kayıt ===================== */
48
49  function save() {
50    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (e) {}
51  }
52
53  function load() {
54    try {
55      var saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
56      if (saved.modes) state.modes = saved.modes;
57      if (typeof saved.scale === "number") {
58        state.scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, saved.scale));
59      }
60    } catch (e) {}
61  }
62
63  /* ===================== Modlar ===================== */
64
65  function isOn(name) { return state.modes[name] === true; }
66
67  function setMode(name, on) {
68    var cfg = MODES[name];
69    if (!cfg) return;
70
71    if (on && cfg.group) {
72      Object.keys(MODES).forEach(function (other) {
73        if (other !== name && MODES[other].group === cfg.group && isOn(other)) {
74          state.modes[other] = false;
75          root.classList.remove(other);
76        }
77      });
78    }
79
80    state.modes[name] = on;
81    if (!cfg.special) root.classList.toggle(name, on);
82
83    if (name === "reading-guide") showGuide(on);
84    if (name === "reading-mask")  showMask(on);
85    if (name === "read-mode" && !on) stopSpeech();
86
87    syncButtons();
88    save();
89  }
90
91  function applyAllModes() {
92    Object.keys(MODES).forEach(function (name) {
93      var on = isOn(name);
94      if (!MODES[name].special) root.classList.toggle(name, on);
95    });
96    showGuide(isOn("reading-guide"));
97    showMask(isOn("reading-mask"));
98  }
99
100  /* ===================== Yazı boyutu =====================
101     Site fontları px ile tanımlı olduğundan body'ye yüzde vermek yetmez.
102     Her öğenin ilk hesaplanan boyutu bir kez saklanır, her değişiklikte
103     hep o temel değer üzerinden hesaplanır (kayma olmaz). */
104
105  function applyScale() {
106    var factor = state.scale / 100;
107    var nodes = document.querySelectorAll(TEXT_SELECTOR);
108
109    for (var i = 0; i < nodes.length; i++) {
110      var el = nodes[i];
111      if (el.closest("#a11y")) continue;
112
113      if (!el.dataset.a11yBase) {
114        var px = parseFloat(window.getComputedStyle(el).fontSize);
115        if (!px) continue;
116        el.dataset.a11yBase = String(px);
117      }
118      el.style.fontSize = (factor === 1)
119        ? ""
120        : (parseFloat(el.dataset.a11yBase) * factor).toFixed(2) + "px";
121    }
122    syncButtons();
123  }
124
125  function changeScale(delta) {
126    var next = Math.min(MAX_SCALE, Math.max(MIN_SCALE, state.scale + delta));
127    if (next === state.scale) { flashLimit(); return; }
128    state.scale = next;
129    applyScale();
130    save();
131  }
132
133  function resetScale() {
134    state.scale = 100;
135    applyScale();
136    save();
137  }
138
139  function flashLimit() {
140    if (!sizeLabel) return;
141    sizeLabel.style.color = "#b02a26";
142    setTimeout(function () { sizeLabel.style.color = ""; }, 400);
143  }
144
145  /* ===================== Kılavuz / maske ===================== */
146
147  function buildOverlays() {
148    guideEl    = make("a11y-guide");
149    maskTop    = make("a11y-mask-top");
150    maskBottom = make("a11y-mask-bottom");
151
152    document.addEventListener("mousemove", function (e) { movePointer(e.clientY); }, { passive: true });
153    document.addEventListener("touchmove", function (e) {
154      if (e.touches[0]) movePointer(e.touches[0].clientY);
155    }, { passive: true });
156    window.addEventListener("resize", function () { movePointer(pointerY); });
157
158    function make(cls) {
159      var d = document.createElement("div");
160      d.className = cls;
161      d.hidden = true;
162      document.body.appendChild(d);
163      return d;
164    }
165  }
166
167  function movePointer(y) {
168    if (y == null) y = window.innerHeight / 2;
169    pointerY = y;
170
171    if (isOn("reading-guide")) guideEl.style.top = (y - 2) + "px";
172
173    if (isOn("reading-mask")) {
174      var band = 64;                                  // okunabilir şerit yüksekliği
175      maskTop.style.top = "0px";
176      maskTop.style.height = Math.max(0, y - band / 2) + "px";
177      maskBottom.style.top = (y + band / 2) + "px";
178      maskBottom.style.height = Math.max(0, window.innerHeight - y - band / 2) + "px";
179    }
180  }
181
182  function showGuide(on) {
183    if (!guideEl) return;
184    guideEl.hidden = !on;
185    if (on) movePointer(pointerY);                    // açılır açılmaz konumlansın
186  }
187
188  function showMask(on) {
189    if (!maskTop) return;
190    maskTop.hidden = maskBottom.hidden = !on;
191    if (on) movePointer(pointerY);
192  }
193
194  /* ===================== Sesli okuma ===================== */
195
196  var synth = window.speechSynthesis || null;
197  var pending = null;
198  var keepAlive = null;
199
200  function currentLang() {
201    return /(^|;\s*)googtrans=/.test(document.cookie) ? "en-US" : "tr-TR";
202  }
203
204  function pickVoice(lang) {
205    var voices = synth.getVoices() || [];
206    var base = lang.split("-")[0];
207    var exact = voices.filter(function (v) { return v.lang && v.lang.replace("_", "-") === lang; })[0];
208    if (exact) return exact;
209    return voices.filter(function (v) { return v.lang && v.lang.
209indexOf(base) === 0; })[0] || null;
210  }
211
212  function speak(text) {
213    if (!synth || !text) return;
214    var clean = String(text).replace(/\s+/g, " ").trim();
215    if (!clean) return;
216
217    synth.cancel();
218
219    // Sesler ilk çağrıda henüz yüklenmemiş olabilir: yüklenince tekrar dene
220    if (!synth.getVoices().length && !pending) {
221      pending = clean;
222      synth.addEventListener("voiceschanged", function once() {
223        synth.removeEventListener("voiceschanged", once);
224        var t = pending; pending = null;
225        if (t) speak(t);
226      });
227      setTimeout(function () { var t = pending; pending = null; if (t) doSpeak(t); }, 800);
228      return;
229    }
230    doSpeak(clean);
231  }
232
233  function doSpeak(clean) {
234    var lang = currentLang();
235    var voice = pickVoice(lang);
236
237    chunk(clean, 200).forEach(function (part) {
238      var u = new SpeechSynthesisUtterance(part);
239      u.lang = lang;
240      if (voice) u.voice = voice;
241      u.volume = 1; u.rate = 1; u.pitch = 1;
242      synth.speak(u);
243    });
244
245    // Chrome uzun metinde ~15 sn sonra susar; düzenli resume ile canlı tutulur
246    clearInterval(keepAlive);
247    keepAlive = setInterval(function () {
248      if (!synth.speaking) { clearInterval(keepAlive); return; }
249      synth.pause(); synth.resume();
250    }, 10000);
251  }
252
253  function chunk(text, size) {
254    var parts = text.match(/[^.!?…\n]+[.!?…]*\s*/g) || [text];
255    var out = [], buf = "";
256    parts.forEach(function (s) {
257      if ((buf + s).length > size && buf) { out.push(buf); buf = ""; }
258      buf += s;
259      while (buf.length > size) { out.push(buf.slice(0, size)); buf = buf.slice(size); }
260    });
261    if (buf.trim()) out.push(buf);
262    return out;
263  }
264
265  function stopSpeech() {
266    if (synth) synth.cancel();
267    clearInterval(keepAlive);
268    pending = null;
269    if (lastSpoken) { lastSpoken.classList.remove("a11y-speaking"); lastSpoken = null; }
270  }
271
272  function readPage() {
273    var source = document.querySelector("main, [role=main], #content, .content, .container") || document.body;
274    var copy = source.cloneNode(true);
275    copy.querySelectorAll("#a11y, script, style, noscript, svg, .a11y-guide, .a11y-mask-top, .a11y-mask-bottom")
276        .forEach(function (n) { n.remove(); });
277    speak(copy.textContent);
278  }
279
280  function onReadModeClick(e) {
281    if (!isOn("read-mode")) return;
282    if (e.target.closest("#a11y")) return;
283    if (e.target.closest("#Oku, #Sus, #Buyut, #Kucult, #Normal")) return;
284
285    var el = e.target.closest(
286      "p,li,a,h1,h2,h3,h4,h5,h6,td,th,button,label,figcaption,blockquote,span,div");
287    if (!el) return;
288
289    var text = el.innerText || el.textContent;
290    if (!text || !text.trim()) return;
291
292    if (lastSpoken) lastSpoken.classList.remove("a11y-speaking");
293    el.classList.add("a11y-speaking");
294    lastSpoken = el;
295
296    speak(text);        // preventDefault yok — bağlantılar çalışmaya devam eder
297  }
298
299  /* ===================== Sıfırlama ===================== */
300
301  function resetAll() {
302    stopSpeech();
303    Object.keys(MODES).forEach(function (name) {
304      state.modes[name] = false;
305      root.classList.remove(name);
306    });
307    showGuide(false);
308    showMask(false);
309
310    state.scale = 100;
311    document.querySelectorAll("[data-a11y-base]").forEach(function (el) {
312      el.style.fontSize = "";
313      delete el.dataset.a11yBase;
314    });
315
316    try { localStorage.removeItem(STORAGE_KEY); } catch (e) {}
317    syncButtons();
318  }
319
320  /* ===================== Panel ===================== */
321
322  function openPanel(open) {
323    if (!panel) return;
324    panel.hidden = !open;
325    toggleBtn.setAttribute("aria-expanded", String(open));
326    toggleBtn.setAttribute("aria-label",
327      open ? "Erişilebilirlik menüsünü kapat" : "Erişilebilirlik menüsünü aç");
328    if (open) {
329      var first = panel.querySelector("button");
330      if (first) first.focus();
331    }
332  }
333
334  /* Panel içi + sayfadaki tüm kontroller + eski butonlar tek yerden güncellenir */
335  function syncButtons() {
336    document.querySelectorAll("[data-toggle]").forEach(function (btn) {
337      if (MODES[btn.dataset.toggle]) {
338        btn.setAttribute("aria-pressed", String(isOn(btn.dataset.toggle)));
339      }
340    });
341    if (sizeLabel) sizeLabel.textContent = "%" + state.scale;
342
343    // Eski üst bar butonlarının aktif rengi
344    mark("#Oku", isOn("read-mode"));
345    mark("#Sus", !isOn("read-mode"));
346    mark("#Buyut", state.scale !== 100);
347    mark("#Kucult", state.scale !== 100);
348
349    function mark(sel, on) {
350      var el = document.querySelector(sel);
351      if (el) el.style.backgroundColor = on ? "#ab0303" : "";
352    }
353  }
354
355  /* ===================== Olaylar ===================== */
356
357  var LEGACY = {
358    "Buyut":  function () { changeScale(STEP); },
359    "Kucult": function () { changeScale(-STEP); },
360    "Normal": function () { resetScale(); },
361    "Oku":    function () {
362      setMode("read-mode", true);
363      speak("Okuma modu açıldı. Dinlemek istediğiniz metne tıklayın.");
364    },
365    "Sus":    function () { setMode("read-mode", false); stopSpeech(); }
366  };
367
368  function onClick(e) {
369    var btn = e.target.closest("button, a[data-action], a[data-toggle], [role=button]");
370    if (!btn) return;
371
372    // 1) Eski üst bar butonları
373    if (btn.id && LEGACY[btn.id]) { e.preventDefault(); LEGACY[btn.id](); return; }
374
375    // 2) Mod düğmeleri (sayfanın her yerinde çalışır)
376    if (btn.dataset.toggle && MODES[btn.dataset.toggle]) {
377      e.preventDefault();
378      var name = btn.dataset.toggle;
379      setMode(name, !isOn(name));
380      if (name === "read-mode" && isOn(name)) {
381        speak("Okuma modu açıldı. Dinlemek istediğiniz metne tıklayın.");
382      }
383      return;
384    }
385
386    // 3) Eylem düğmeleri
387    switch (btn.dataset.action) {
388      case "increaseText": e.preventDefault(); changeScale(STEP); break;
389      case "decreaseText": e.preventDefault(); changeScale(-STEP); break;
390      case "normalText":   e.preventDefault(); resetScale(); break;
391      case "readPage":     e.preventDefault(); readPage(); break;
392      case "stopRead":     e.preventDefault(); stopSpeech(); break;
393      case "reset":        e.preventDefault(); resetAll(); break;
394      case "close":        e.preventDefault(); openPanel(false); toggleBtn.focus(); break;
395    }
396  }
397
398  function bind() {
399    if (toggleBtn) {
400      toggleBtn.addEventListener("click", function (e) {
401        e.stopPropagation();
402        openPanel(panel.hidden);
403      });
404    }
405
406    document.addEventListener("click", onClick);
407    document.addEventListener("click", onReadModeClick, true);
408
409    document.addEventListener("click", function (e) {
410      if (panel && !panel.hidden && !e.target.closest("#a11y")) openPanel
410(false);
411    });
412
413    document.addEventListener("keydown", function (e) {
414      if (e.key === "Escape" && panel && !panel.hidden) { openPanel(false); toggleBtn.focus(); }
415    });
416
417    window.addEventListener("beforeunload", stopSpeech);
418
419    // Sesli okuma desteklenmiyorsa ilgili butonları devre dışı bırak
420    if (!synth) {
421      document.querySelectorAll(
422        '[data-action="readPage"],[data-action="stopRead"],[data-toggle="read-mode"],#Oku,#Sus'
423      ).forEach(function (b) {
424        b.disabled = true;
425        b.title = "Tarayıcınız sesli okumayı desteklemiyor";
426        b.style.opacity = ".5";
427      });
428    }
429  }
430
431  /* ===================== Başlangıç ===================== */
432
433  function init() {
434    panel     = document.getElementById("a11yPanel");
435    toggleBtn = document.getElementById("a11yToggle");
436    sizeLabel = document.getElementById("a11ySizeLabel");
437
438    buildOverlays();
439    load();
440    applyAllModes();
441    applyScale();
442    bind();
443    syncButtons();
444  }
445
446  if (document.readyState === "loading") {
447    document.addEventListener("DOMContentLoaded", init);
448  } else {
449    init();
450  }
451})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.