Sprečeno dupliranje prodaje/servisa, alat za usklađivanje šifri artikla, ispravke osvežavanja stranica, zaštita od duplikata kategorije/artikla/usluge/troška

- Prodaja i servis: broj naloga se generiše unutar transakcije upisa umesto unapred u handleru, čime se otklanja duplo kreiranje naloga pri dvostrukom slanju forme
- Servis izmena: uklonjen sudar generičkog i sopstvenog auto-snimanja forme (dva paralelna POST-a)
- Magacin: novi alat 'Uskladi šifre sa kategorijama' za masovnu ispravku šifri artikala posle uvođenja šifarnika po kategoriji
- Ispravljeno da forme na 9 stranica (kategorije, admin korisnici/dozvole, profil, podešavanja) posle akcije osveže prikaz umesto da ostanu zastarele do F5
- Kategorija, artikal, usluga i trošak: sprečeno dupliranje naziva/šifre/koda (UNIQUE indeksi + razumljive poruke grešaka)
This commit is contained in:
2026-07-07 03:50:31 +02:00
parent 1ff572dd77
commit bc60288a0d
28 changed files with 682 additions and 110 deletions
+23
View File
@@ -1,3 +1,26 @@
// generička zaštita od duplog slanja forme: onemogući submit dugme(ad) čim
// forma krene da se šalje (spor internet/dupli klik/Enter+klik ne pravi drugi
// POST). Dodaj data-ne-onemoguci="1" na formu da se isključi (npr. forme sa
// više imenovanih submit dugmadi gde različit klik nosi različitu vrednost).
document.addEventListener('submit', function(e) {
var forma = e.target;
if (!(forma instanceof HTMLFormElement)) return;
if (forma.dataset.neOnemoguci === '1') return;
var dugmad = forma.querySelectorAll('button[type="submit"]:not([name]), input[type="submit"]:not([name])');
setTimeout(function() {
dugmad.forEach(function(btn) { btn.disabled = true; });
}, 0);
}, true);
// ako se stranica vrati iz bfcache-a (dugme nazad/napred), vrati dugmad u
// normalno stanje da forma ostane upotrebljiva
window.addEventListener('pageshow', function(e) {
if (!e.persisted) return;
document.querySelectorAll('button[type="submit"]:disabled, input[type="submit"]:disabled').forEach(function(btn) {
btn.disabled = false;
});
});
// otvara/zatvara podmeni u sidebaru — radi i kad je sidebar skupljen i kad je proširen
// (sidebar ostaje u zatečenom stanju). U isto vreme sme biti otvoren samo jedan podmeni.
function ntechTogglePodmeni(btn) {
+3 -3
View File
@@ -32,11 +32,11 @@
<div class="kolona" style="gap:20px;">
<!-- matrica dozvola — editabilna forma -->
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
<div id="dozvole-kartica" class="kartica animiraj" style="padding:0;overflow:hidden;">
<div style="padding:14px 16px;border-bottom:0.5px solid var(--ivica);">
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Dozvole po ulogama</span>
</div>
<form method="POST" action="/admin/dozvole/sacuvaj">
<form method="POST" action="/admin/dozvole/sacuvaj" data-refresh="#dozvole-kartica">
<div style="overflow-x:auto;">
<table class="tabela">
<thead>
@@ -299,7 +299,7 @@
</form>
<!-- zasebna forma za reset, bez vidljivih elemenata -->
<form id="forma-reset" method="POST" action="/admin/dozvole/reset"></form>
<form id="forma-reset" method="POST" action="/admin/dozvole/reset" data-refresh="#dozvole-kartica"></form>
</div>
</div>
+3 -3
View File
@@ -38,7 +38,7 @@
{{if eq .Uloga "superadmin"}}
<span class="pomocni-tekst">Superadmin</span>
{{else if eq $.KorisnikUloga "superadmin"}}
<form method="POST" action="/admin/korisnici/{{.ID}}/uloga" style="display:inline;">
<form method="POST" action="/admin/korisnici/{{.ID}}/uloga" data-refresh="#lista-korisnika-kartica" style="display:inline;">
<select name="uloga" onchange="this.form.submit()"
style="padding:4px 8px;border:0.5px solid var(--ivica);border-radius:6px;background:var(--pozadina);color:var(--tekst-glavni);font-size:12px;">
<option value="admin" {{if eq .Uloga "admin"}}selected{{end}}>Admin</option>
@@ -74,14 +74,14 @@
Istorija prijava
</a>
{{if ne .KorisnickoIme $.KorisnikIme}}
<form method="POST" action="/admin/korisnici/{{.ID}}/aktivan" style="display:inline;">
<form method="POST" action="/admin/korisnici/{{.ID}}/aktivan" data-refresh="#lista-korisnika-kartica" style="display:inline;">
<button type="submit"
style="padding:5px 12px;border:0.5px solid var(--ivica);border-radius:6px;background:transparent;color:var(--tekst-sporedni);font-size:12px;cursor:pointer;">
{{if .Aktivan}}Deaktiviraj{{else}}Aktiviraj{{end}}
</button>
</form>
{{if eq .Uloga "radnik"}}
<form method="POST" action="/admin/korisnici/{{.ID}}/obrisi" style="display:inline;">
<form method="POST" action="/admin/korisnici/{{.ID}}/obrisi" data-refresh="#lista-korisnika-kartica" style="display:inline;">
<button type="submit"
class="btn-obrisi-ghost"
data-potvrda="Obrisati korisnika {{.KorisnickoIme}}? Ova radnja je trajna.">
+27 -19
View File
@@ -19,17 +19,19 @@
{{define "sadrzaj"}}
<div class="kolona" style="gap:16px;">
{{if .Sacuvano}}
<div class="poruka-uspeh poruka-animacija">Kategorija je uspešno sačuvana.</div>
{{end}}
{{if .Obrisana}}
<div class="poruka-uspeh poruka-animacija">Kategorija je uspešno obrisana.</div>
{{end}}
{{if .PrikaziSifreDod}}
<div class="poruka-uspeh poruka-animacija">
{{if eq .SifreDodeljene 0}}Svi artikli već imaju šifru.{{else}}Dodeljena šifra za {{.SifreDodeljene}} artikal(a).{{end}}
<div id="kat-poruke">
{{if .Sacuvano}}
<div class="poruka-uspeh poruka-animacija">Kategorija je uspešno sačuvana.</div>
{{end}}
{{if .Obrisana}}
<div class="poruka-uspeh poruka-animacija">Kategorija je uspešno obrisana.</div>
{{end}}
{{if .PrikaziSifreDod}}
<div class="poruka-uspeh poruka-animacija">
{{if eq .SifreDodeljene 0}}Svi artikli već imaju šifru.{{else}}Dodeljena šifra za {{.SifreDodeljene}} artikal(a).{{end}}
</div>
{{end}}
</div>
{{end}}
<!-- nazad dugme -->
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;">
@@ -38,12 +40,18 @@
Nazad na magacin
</a>
{{if index $.Dozvole "artikal.izmeni"}}
<form method="POST" action="/magacin/kategorije/dodeli-sifre">
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;"
data-potvrda="Proći kroz sve artikle bez šifre i dodeliti im šifru na osnovu prefiksa njihove kategorije?">
Dodeli šifre artiklima bez šifre
</button>
</form>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<form method="POST" action="/magacin/kategorije/dodeli-sifre" data-refresh="#kat-poruke, #kategorije-lista">
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;"
data-potvrda="Proći kroz sve artikle bez šifre i dodeliti im šifru na osnovu prefiksa njihove kategorije?">
Dodeli šifre artiklima bez šifre
</button>
</form>
<a href="/magacin/kategorije/uskladi-sifre" class="btn-sekundarno" style="font-size:13px;padding:8px 16px;"
hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
Uskladi šifre sa kategorijama
</a>
</div>
{{end}}
</div>
@@ -53,7 +61,7 @@
<div style="margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Nova kategorija</span>
</div>
<form method="POST" action="/magacin/kategorije/dodaj">
<form method="POST" action="/magacin/kategorije/dodaj" data-refresh="#kat-poruke, #kategorije-lista">
<div class="kolona" style="gap:12px;">
<div>
<label class="polje-labela">
@@ -90,7 +98,7 @@
{{end}}
<!-- lista kategorija -->
<div class="kartica" style="padding:0;overflow:hidden;">
<div id="kategorije-lista" class="kartica" style="padding:0;overflow:hidden;">
<div style="padding:14px 16px;border-bottom:0.5px solid var(--ivica);">
<span style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">Postojeće kategorije</span>
</div>
@@ -116,7 +124,7 @@
<h3>Izmeni kategoriju</h3>
<button type="submit" class="premesti-zatvori" aria-label="Zatvori">&times;</button>
</form>
<form method="POST" action="/magacin/kategorije/izmeni/{{.ID}}" style="display:flex;flex-direction:column;gap:12px;padding:16px;">
<form method="POST" action="/magacin/kategorije/izmeni/{{.ID}}" data-refresh="#kat-poruke, #kategorije-lista" style="display:flex;flex-direction:column;gap:12px;padding:16px;">
<div>
<label class="polje-labela">Naziv <span style="color:#dc2626;">*</span></label>
<input type="text" name="naziv" value="{{.Naziv}}" required
@@ -0,0 +1,63 @@
{{template "base" .}}
{{define "naslov"}}Usklađivanje šifri — NTech{{end}}
{{define "sadrzaj"}}
<div class="kolona" style="gap:16px;">
<!-- nazad dugme -->
<div>
<a href="/magacin/kategorije" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
Nazad na kategorije
</a>
</div>
{{if not .Plan}}
<div class="kartica animiraj">
<p style="margin:0;color:var(--tekst-sekundarni);">Sve šifre su već usklađene sa prefiksima svojih kategorija.</p>
</div>
{{else}}
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
<div style="padding:14px 16px;border-bottom:0.5px solid var(--ivica);">
<span style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">Predložene izmene ({{len .Plan}})</span>
<p class="pomocni-tekst" style="margin:4px 0 0;">Ovo su artikli čija šifra ne odgovara prefiksu njihove kategorije. Pregledaj i potvrdi.</p>
</div>
<div style="overflow-x:auto;">
<table class="tabela">
<thead>
<tr>
<th>Artikal</th>
<th>Kategorija</th>
<th>Stara šifra</th>
<th>Nova šifra</th>
</tr>
</thead>
<tbody>
{{range .Plan}}
<tr>
<td>{{.Naziv}}</td>
<td>{{if .KategorijaNaziv}}{{.KategorijaNaziv}}{{else}}—{{end}}</td>
<td>{{if .StaraSifra}}{{.StaraSifra}}{{else}}<em>(bez šifre)</em>{{end}}</td>
<td><strong>{{.NovaSifra}}</strong></td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
<form method="POST" action="/magacin/kategorije/uskladi-sifre" style="align-self:flex-end;">
{{range .Plan}}
<input type="hidden" name="artikal_id" value="{{.ArtikalID}}">
<input type="hidden" name="nova_sifra" value="{{.NovaSifra}}">
{{end}}
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:8px 20px;"
data-potvrda="Primeniti prikazane izmene šifri za {{len .Plan}} artikal(a)?">
Potvrdi izmenu
</button>
</form>
{{end}}
</div>
{{end}}
+15 -13
View File
@@ -16,20 +16,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Pozadinska slika prijave</span>
</div>
{{if .LoginPozadina}}
<div style="margin-bottom:14px;display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;">
<img src="{{.LoginPozadina}}" alt="Trenutna pozadina"
style="width:160px;height:90px;object-fit:cover;border:0.5px solid var(--ivica);border-radius:8px;">
<form method="POST" action="/podesavanja/login-pozadina/ukloni">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
class="btn-obrisi-ghost" style="padding:7px 14px;border-radius:8px;"
data-potvrda="Ukloniti pozadinsku sliku?">
Ukloni sliku
</button>
</form>
<div id="login-pozadina-blok">
{{if .LoginPozadina}}
<div style="margin-bottom:14px;display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;">
<img src="{{.LoginPozadina}}" alt="Trenutna pozadina"
style="width:160px;height:90px;object-fit:cover;border:0.5px solid var(--ivica);border-radius:8px;">
<form method="POST" action="/podesavanja/login-pozadina/ukloni" data-refresh="#login-pozadina-blok">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
class="btn-obrisi-ghost" style="padding:7px 14px;border-radius:8px;"
data-potvrda="Ukloniti pozadinsku sliku?">
Ukloni sliku
</button>
</form>
</div>
{{end}}
</div>
{{end}}
<form method="POST" action="/podesavanja/login-pozadina" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
+14 -12
View File
@@ -15,19 +15,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Logo firme</span>
</div>
{{if .LogoPutanja}}
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
<img src="{{.LogoPutanja}}" alt="Trenutni logo"
style="max-height:60px;max-width:200px;object-fit:contain;border:0.5px solid var(--ivica);border-radius:8px;padding:6px;background:var(--kartica);flex-shrink:0;">
<form method="POST" action="/podesavanja/logo/ukloni" style="margin:0;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
style="padding:6px 14px;background:none;border:0.5px solid #dc2626;color:#dc2626;border-radius:8px;font-size:13px;cursor:pointer;white-space:nowrap;">
Ukloni sliku
</button>
</form>
<div id="logo-blok">
{{if .LogoPutanja}}
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
<img src="{{.LogoPutanja}}" alt="Trenutni logo"
style="max-height:60px;max-width:200px;object-fit:contain;border:0.5px solid var(--ivica);border-radius:8px;padding:6px;background:var(--kartica);flex-shrink:0;">
<form method="POST" action="/podesavanja/logo/ukloni" data-refresh="#logo-blok" style="margin:0;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
style="padding:6px 14px;background:none;border:0.5px solid #dc2626;color:#dc2626;border-radius:8px;font-size:13px;cursor:pointer;white-space:nowrap;">
Ukloni sliku
</button>
</form>
</div>
{{end}}
</div>
{{end}}
<form method="POST" action="/podesavanja/logo" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
+37 -33
View File
@@ -192,28 +192,30 @@
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Moj avatar</span>
</div>
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
<div style="width:64px;height:64px;border-radius:10px;overflow:hidden;border:0.5px solid var(--ivica);flex-shrink:0;background:var(--pozadina);display:flex;align-items:center;justify-content:center;">
{{if .AvatarPutanja}}
<img src="{{.AvatarPutanja}}" alt="Moj avatar" style="width:100%;height:100%;object-fit:cover;">
{{else}}
<span style="font-size:22px;font-weight:600;color:var(--tekst-sporedni);">{{if .KorisnikIme}}{{slice .KorisnikIme 0 2}}{{else}}NT{{end}}</span>
{{end}}
<div id="avatar-blok">
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
<div style="width:64px;height:64px;border-radius:10px;overflow:hidden;border:0.5px solid var(--ivica);flex-shrink:0;background:var(--pozadina);display:flex;align-items:center;justify-content:center;">
{{if .AvatarPutanja}}
<img src="{{.AvatarPutanja}}" alt="Moj avatar" style="width:100%;height:100%;object-fit:cover;">
{{else}}
<span style="font-size:22px;font-weight:600;color:var(--tekst-sporedni);">{{if .KorisnikIme}}{{slice .KorisnikIme 0 2}}{{else}}NT{{end}}</span>
{{end}}
</div>
<div style="font-size:13px;color:var(--tekst-sporedni);line-height:1.5;">
Slika se prikazuje kao dugme u gornjoj traci.<br>
Dozvoljeni formati: JPG, PNG, WebP. Maksimum 2 MB.
</div>
</div>
<div style="font-size:13px;color:var(--tekst-sporedni);line-height:1.5;">
Slika se prikazuje kao dugme u gornjoj traci.<br>
Dozvoljeni formati: JPG, PNG, WebP. Maksimum 2 MB.
</div>
</div>
{{if .AvatarPutanja}}
<form method="POST" action="/profil/avatar/ukloni" style="display:inline;margin-bottom:12px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit" style="background:none;border:0.5px solid #dc2626;color:#dc2626;padding:6px 14px;border-radius:8px;font-size:13px;cursor:pointer;margin-bottom:12px;">
Ukloni avatar
</button>
</form>
{{end}}
{{if .AvatarPutanja}}
<form method="POST" action="/profil/avatar/ukloni" data-refresh="#avatar-blok" style="display:inline;margin-bottom:12px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit" style="background:none;border:0.5px solid #dc2626;color:#dc2626;padding:6px 14px;border-radius:8px;font-size:13px;cursor:pointer;margin-bottom:12px;">
Ukloni avatar
</button>
</form>
{{end}}
</div>
<form method="POST" action="/profil/avatar" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
@@ -240,20 +242,22 @@
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Moja pozadinska slika</span>
</div>
{{if .LokalnaPozadina}}
<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
<img src="{{.LokalnaPozadina}}" alt="Trenutna lična pozadina"
style="width:120px;height:70px;object-fit:cover;border-radius:6px;border:0.5px solid var(--ivica);flex-shrink:0;">
<form method="POST" action="/profil/pozadina/ukloni">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
class="btn-obrisi-ghost" style="padding:7px 14px;border-radius:8px;font-size:13px;"
data-potvrda="Ukloniti ličnu pozadinsku sliku?">
Ukloni
</button>
</form>
<div id="pozadina-blok">
{{if .LokalnaPozadina}}
<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
<img src="{{.LokalnaPozadina}}" alt="Trenutna lična pozadina"
style="width:120px;height:70px;object-fit:cover;border-radius:6px;border:0.5px solid var(--ivica);flex-shrink:0;">
<form method="POST" action="/profil/pozadina/ukloni" data-refresh="#pozadina-blok">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
class="btn-obrisi-ghost" style="padding:7px 14px;border-radius:8px;font-size:13px;"
data-potvrda="Ukloniti ličnu pozadinsku sliku?">
Ukloni
</button>
</form>
</div>
{{end}}
</div>
{{end}}
<form method="POST" action="/profil/pozadina" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
+6 -2
View File
@@ -25,7 +25,11 @@
<div class="poruka-greska greska-animacija" style="margin-bottom:16px;">{{.Greska}}</div>
{{end}}
<form id="forma-nalog" method="POST" action="{{if .Izmena}}/servis/izmeni/{{.Nalog.ID}}{{else}}/servis/novi{{end}}"{{if .Izmena}} data-autosave="1"{{end}}>
<!-- data-autosave-custom (NE data-autosave) — ova forma ima SOPSTVENI auto-save skript
ispod (šalje X-Autosave header, prikazuje status "Čuvam.../Sačuvano ✓"). Generički
data-autosave mehanizam iz base.html bi se kačio na isti 'change' event i slao
DRUGI, konfliktni POST bez tog headera — otud namerno drugačije ime atributa. -->
<form id="forma-nalog" method="POST" action="{{if .Izmena}}/servis/izmeni/{{.Nalog.ID}}{{else}}/servis/novi{{end}}"{{if .Izmena}} data-autosave-custom="1"{{end}}>
<!-- broj naloga ide kao hidden — server ga je generisao, vraćamo ga pri POST-u -->
<input type="hidden" name="broj_naloga" value="{{.Nalog.BrojNaloga}}">
@@ -311,7 +315,7 @@
// a za select/date/checkbox na izbor — jedan slušalac pokriva sve tipove.
(function () {
var forma = document.getElementById('forma-nalog');
if (!forma || forma.dataset.autosave !== '1') return;
if (!forma || forma.dataset.autosaveCustom !== '1') return;
var status = document.getElementById('autosave-status');
var slanjeUToku = false;
var ponoviPoZavrsetku = false;
@@ -441,6 +441,23 @@
});
}
});
// flash poruka (SetFlash, npr. greška validacije) možda NIJE postojala
// u trenutnom DOM-u pre ovog zahteva — replaceWith gore je ne bi ubacio
// (menja samo postojeće elemente), pa je ovde dodajemo posebno ako se
// pojavila u svežem odgovoru.
var flashNovi = doc.getElementById('flash-toast');
if (flashNovi) {
var flashStari = document.getElementById('flash-toast');
if (flashStari) flashStari.remove();
var glavni = document.getElementById('sadrzaj');
if (glavni) {
glavni.insertBefore(flashNovi, glavni.firstChild);
setTimeout(function () {
flashNovi.classList.add('flash-izlaz');
setTimeout(function () { if (flashNovi.parentElement) flashNovi.remove(); }, 350);
}, 4000);
}
}
if (sc) sc.scrollTop = syDoc;
var gsNovi = document.getElementById('sadrzaj');
if (gsNovi) gsNovi.scrollTop = syGs;