Upload via GUI (33 Dateien)
This commit is contained in:
@@ -5,20 +5,24 @@ das, was sonst eine "neue Tab"-Seite im Browser wäre, aber als schnelles,
|
||||
eigenes Programm: mit Lesezeichen, Gruppen, Design-Optionen, Uhr/Datum und
|
||||
allem, was eine gute Startseite braucht. Sie basiert auf dem quelloffenen
|
||||
Projekt **nightTab**, komplett ins Deutsche übersetzt und um einige eigene
|
||||
Funktionen erweitert (Fenster-Bedienung, mehrere Reiter, Cloud-Abgleich).
|
||||
Funktionen erweitert (Fenster-Bedienung, mehrere Reiter, Abgleich zwischen
|
||||
mehreren PCs, eigene Bilder und Videos von deinem Rechner).
|
||||
|
||||
---
|
||||
|
||||
## Installation & erster Start
|
||||
|
||||
MyStart ist eine **portable Einzeldatei** (`MyStart.exe`, ca. 48 MB) – kein
|
||||
MyStart ist eine **portable Einzeldatei** (`MyStart.exe`, ca. 110 MB) – kein
|
||||
Installer, keine Admin-Rechte nötig. Einfach an einen beliebigen Ort legen
|
||||
und starten.
|
||||
und starten. Die Datei ist bewusst unkomprimiert und daher größer; sie muss
|
||||
sich beim Start nicht selbst entpacken.
|
||||
|
||||
- **Erster Start:** ca. 2 Sekunden, danach öffnet sich MyStart praktisch
|
||||
sofort.
|
||||
- Die App startet als **normales Fenster** (nicht im Vollbild), zentriert
|
||||
auf dem Bildschirm.
|
||||
- Beim allerersten Start öffnet sich die App als **normales Fenster** (nicht
|
||||
im Vollbild), zentriert auf dem Bildschirm. Danach merkt sie sich Größe,
|
||||
Position und ob du sie maximiert hattest, und startet dort wieder, wo du
|
||||
sie zuletzt gelassen hast (siehe unten).
|
||||
- Alle deine Daten (Lesezeichen, Design, Reiter …) werden lokal unter
|
||||
`%LOCALAPPDATA%\MyStart` gespeichert – unabhängig davon, wo die `.exe`
|
||||
liegt oder ob du sie verschiebst.
|
||||
@@ -43,6 +47,11 @@ Zusätzlich:
|
||||
- **Größe ändern:** einfach an einer beliebigen Fensterkante oder -ecke
|
||||
ziehen, genau wie bei einem normalen Windows-Fenster.
|
||||
- Ein maximiertes Fenster deckt nie die Taskleiste ab.
|
||||
- **Größe und Position merken:** Beim Schließen wird die aktuelle Größe,
|
||||
Position und ob maximiert gespeichert und beim nächsten Start wieder
|
||||
angewendet. Passt die gespeicherte Position auf keinen aktuell
|
||||
angeschlossenen Bildschirm mehr (z. B. zweiter Monitor abgesteckt), startet
|
||||
MyStart stattdessen wieder zentriert in der Standardgröße.
|
||||
|
||||
---
|
||||
|
||||
@@ -69,12 +78,13 @@ eigenen** Lesezeichen, Gruppen und sein eigenes Design.
|
||||
|
||||
---
|
||||
|
||||
## Automatischer Abgleich über OneDrive
|
||||
## Abgleich zwischen mehreren PCs
|
||||
|
||||
Arbeitest du an **mehreren PCs**, kann MyStart seinen kompletten Datenstand
|
||||
(alle Reiter, Lesezeichen, Design) automatisch zwischen den Rechnern
|
||||
abgleichen – sofern auf beiden PCs **OneDrive** eingerichtet und angemeldet
|
||||
ist.
|
||||
abgleichen – über einen synchronisierten Ordner, standardmäßig dein
|
||||
**OneDrive**-Ordner (auf allen beteiligten PCs muss er eingerichtet und
|
||||
angemeldet sein).
|
||||
|
||||
Der Abgleich ist **optional und standardmäßig aus**. Einschalten unter
|
||||
**Einstellungen → MyStart → „Zwischen PCs abgleichen“** – auf jedem PC
|
||||
@@ -88,30 +98,71 @@ synchronisierten Ordner nehmen (Dropbox, Google Drive, Netzlaufwerk …),
|
||||
„Automatisch (OneDrive)“ stellt es zurück. Auf **allen PCs muss derselbe
|
||||
Ordner** genutzt werden, also dieselbe Cloud-Freigabe.
|
||||
|
||||
- MyStart legt dazu im Ordner `OneDrive\MyStart` pro PC eine eigene Datei
|
||||
- MyStart legt dazu im Sync-Ordner pro PC eine eigene Datei
|
||||
`sync-<Rechnername>.json` an (jeder PC schreibt nur seine eigene, damit
|
||||
OneDrive keine Konfliktkopien erzeugt). Die eigentliche Übertragung
|
||||
zwischen den PCs übernimmt OneDrive selbst – MyStart schreibt/liest dort
|
||||
nur diese Dateien.
|
||||
der Cloud-Dienst keine Konfliktkopien erzeugt). Die eigentliche
|
||||
Übertragung zwischen den PCs übernimmt der Cloud-Dienst selbst – MyStart
|
||||
schreibt/liest dort nur diese Dateien.
|
||||
- Abgeglichen wird **alle 30 Sekunden**, außerdem beim Start und beim
|
||||
Schließen der App.
|
||||
- Regel bei Änderungen: **die neueste Version gewinnt.** Ändern beide PCs
|
||||
gleichzeitig etwas, ohne dass zwischendurch synchronisiert wurde, setzt
|
||||
sich schlicht die zuletzt gespeicherte Version durch (kein Zusammenführen
|
||||
einzelner Änderungen).
|
||||
- Regel bei Änderungen: **die neueste Version gewinnt.** Ein PC übernimmt den
|
||||
Stand eines anderen nur, wenn dieser **neuer** ist als sein eigener letzter
|
||||
Stand. Ändern beide PCs gleichzeitig etwas, ohne dass zwischendurch
|
||||
synchronisiert wurde, setzt sich schlicht die zuletzt gespeicherte Version
|
||||
durch (kein Zusammenführen einzelner Änderungen). Dafür sollten die Uhren
|
||||
beider PCs ungefähr richtig gehen (normale Windows-Zeitsynchronisierung
|
||||
reicht).
|
||||
- Ein neuerer Stand vom anderen PC wird erst übernommen, **wenn das
|
||||
MyStart-Fenster gerade nicht aktiv ist** (zum Beispiel wenn du weggeklickt
|
||||
oder es minimiert hast). So lädt die Seite nie mitten in deiner Arbeit neu.
|
||||
- **Bilder und Videos wandern mit:** Verweist ein Reiter auf eine lokale
|
||||
Bild-/Videodatei (siehe nächster Abschnitt), wird die Datei selbst mit in
|
||||
den Sync-Ordner kopiert (Unterordner `media`) und beim anderen PC in einen
|
||||
eigenen lokalen Zwischenspeicher übernommen – das Bild taucht dort also
|
||||
wirklich auf, nicht nur ein toter Pfad.
|
||||
- Ein kleiner **Punkt** in der Reiter-Leiste zeigt den Status (Maus
|
||||
drüberhalten für Details):
|
||||
- 🟢 **grün** – zuletzt erfolgreich abgeglichen
|
||||
- 🟡 **gelb/orange** – aktuell ein Problem (z. B. Datei kurz durch OneDrive
|
||||
gesperrt; behebt sich meist von selbst beim nächsten Abgleich)
|
||||
- Ist auf einem PC kein OneDrive eingerichtet, bleibt der Abgleich dort
|
||||
einfach ohne Fehlermeldung aus – die App funktioniert ganz normal weiter,
|
||||
nur eben ohne Sync.
|
||||
- 🟣 **lila** – ein **Konflikt** wurde erkannt: du hattest hier gerade etwas
|
||||
geändert, während gleichzeitig (ohne zwischenzeitlichen Abgleich) auch
|
||||
auf einem anderen PC etwas geändert wurde. Dein Stand wird trotzdem
|
||||
gespeichert, es erscheint zusätzlich kurz ein Hinweisfenster; schau in
|
||||
dem Fall lieber nochmal nach, ob auf dem anderen PC etwas verloren ging
|
||||
- Findet MyStart auf einem PC keinen OneDrive-Ordner, ist der Schalter
|
||||
ausgegraut, bis du mit „Ordner wählen…“ einen Ordner angibst – die App
|
||||
funktioniert ganz normal weiter, nur eben ohne Sync.
|
||||
- **Wichtig:** Damit der Abgleich funktioniert, muss MyStart auf **jedem**
|
||||
beteiligten PC vorhanden sein und einmal gestartet werden.
|
||||
|
||||
---
|
||||
|
||||
## Eigene Bilder und Videos von diesem PC
|
||||
|
||||
Für Hintergrundbild, Hintergrundvideo, Lesezeichen-Symbole und
|
||||
Lesezeichen-Hintergründe kannst du nicht nur Internet-Adressen, sondern auch
|
||||
**Dateien von deinem PC** nutzen:
|
||||
|
||||
- **„Lokale Datei wählen…“** – neben den Bild-/Video-Feldern in den
|
||||
Einstellungen; öffnet den Windows-Dateidialog. Beim Hintergrundbild
|
||||
(Design) kannst du mehrere Bilder auf einmal wählen.
|
||||
- **Pfad einfügen oder eintippen** geht ebenfalls, zum Beispiel
|
||||
`C:\Bilder\urlaub.jpg`, ein Pfad in Anführungszeichen (so kopiert ihn der
|
||||
Explorer mit „Als Pfad kopieren“), `file:///C:/Bilder/urlaub.jpg` oder ein
|
||||
Netzwerkpfad `\\server\freigabe\bild.png`. Er wird beim Verlassen des
|
||||
Felds automatisch umgewandelt.
|
||||
- Erlaubt sind nur Bild- und Videodateien (PNG, JPG, GIF, WebP, SVG, BMP, ICO,
|
||||
AVIF, MP4, WebM …); andere Dateien werden nie ausgeliefert.
|
||||
|
||||
**Bei mehreren PCs:** Ist der Abgleich (siehe oben) eingeschaltet, wird die
|
||||
Datei selbst mit synchronisiert – sie erscheint dann auch auf dem anderen PC,
|
||||
ganz ohne dass dort derselbe Pfad existieren muss. Ist der Abgleich **aus**,
|
||||
wird nur der Pfad in den Lesezeichen/Design-Daten gespeichert; ohne Sync
|
||||
bleibt es dann bei der Datei, die lokal unter diesem Pfad liegt.
|
||||
|
||||
---
|
||||
|
||||
## Autostart
|
||||
|
||||
Unter **Einstellungen → MyStart** gibt es einen Schalter, um MyStart
|
||||
@@ -145,10 +196,23 @@ Unter **Einstellungen → Daten** kannst du jederzeit:
|
||||
|
||||
---
|
||||
|
||||
## Hinweis zu Windows-Warnungen und Virenscannern
|
||||
|
||||
MyStart ist **nicht digital signiert** (dafür bräuchte es ein
|
||||
kostenpflichtiges Zertifikat). Deshalb kann Windows beim ersten Start
|
||||
„Unbekannter Herausgeber“ melden – über **Weitere Informationen →
|
||||
Trotzdem ausführen** lässt sich das wegklicken. Manche Virenscanner stufen
|
||||
kleine, selbst gebaute und unsignierte Programme zusätzlich fälschlich als
|
||||
verdächtig ein (sogenannter Fehlalarm). Hilft nichts anderes, kannst du für
|
||||
den Ordner mit der `MyStart.exe` eine Ausnahme in deinem Virenscanner
|
||||
eintragen oder die Datei beim Hersteller des Scanners als Fehlalarm melden.
|
||||
|
||||
---
|
||||
|
||||
## Herkunft & Lizenz
|
||||
|
||||
MyStart basiert auf **nightTab** von zombieFox
|
||||
(https://github.com/zombieFox/nightTab), veröffentlicht unter der
|
||||
**GNU General Public License v3.0**. Die Anpassungen in MyStart (deutsche
|
||||
Übersetzung, Fenster-Bedienung, Reiter, OneDrive-Sync u. a.) unterliegen
|
||||
ebenfalls der GPL v3.
|
||||
Übersetzung, Fenster-Bedienung, Reiter, Sync zwischen PCs, lokale Bilder
|
||||
u. a.) unterliegen ebenfalls der GPL v3.
|
||||
|
||||
@@ -39,7 +39,7 @@ const aboutBody =
|
||||
'const o=y("p");o.innerHTML=`Git: ${a.link().outerHTML}`,' +
|
||||
'e.appendChild(y("div",[y("div|class:version",[_t.render(),' +
|
||||
'y("div|class:version-details",[y("h1:MyStart|class:version-app-name"),' +
|
||||
'y("p:Version 1.0.3|class:version-number")])]),y("hr"),s,o,y("div|id:mystart-autostart-row,class:mystart-autostart-row")]))';
|
||||
'y("p:Version 1.0.4|class:version-number")])]),y("hr"),s,o,y("div|id:mystart-autostart-row,class:mystart-autostart-row")]))';
|
||||
mainJs = mainJs.slice(0, i) + aboutBody + mainJs.slice(j + aEndMarker.length);
|
||||
|
||||
// --- 2) Rename branding (visible text + storage keys) consistently ---
|
||||
@@ -247,13 +247,13 @@ const windowControlsJs = `(function(){
|
||||
}
|
||||
document.addEventListener('mousemove',function(ev){
|
||||
if(ev.buttons)return; // an active drag (e.g. scrollbar) is in progress -> leave it alone
|
||||
var onCtrls=ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles');
|
||||
var onCtrls=ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles,#mystart-sync-toast');
|
||||
var edge=onCtrls?null:edgeAt(ev.clientX,ev.clientY);
|
||||
document.documentElement.style.cursor=edge?CURSOR[edge]:'';
|
||||
},true);
|
||||
document.addEventListener('mousedown',function(ev){
|
||||
if(ev.button!==0)return;
|
||||
if(ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles'))return;
|
||||
if(ev.target.closest&&ev.target.closest('#mystart-winctrls,#mystart-profiles,#mystart-sync-toast'))return;
|
||||
var edge=edgeAt(ev.clientX,ev.clientY);
|
||||
if(edge){ev.preventDefault();send2('window-resize',{edge:edge});}
|
||||
},true);
|
||||
@@ -426,8 +426,9 @@ const profilesJs = `(function(){
|
||||
// Sync is opt-in: no dot at all until it's switched on (Einstellungen -> MyStart).
|
||||
if(!lastSync||lastSync.enabled===false){syncDot.style.display='none';return;}
|
||||
syncDot.style.display='';
|
||||
syncDot.style.background=lastSync.ok?'#2ea043':'#e8b923';
|
||||
syncDot.title=lastSync.ok?('OneDrive-Sync: '+lastSync.message):('OneDrive-Sync-Problem: '+lastSync.message);
|
||||
syncDot.style.background=lastSync.conflict?'#a371f7':(lastSync.ok?'#2ea043':'#e8b923');
|
||||
var prefix=lastSync.conflict?'OneDrive-Sync (Konflikt): ':(lastSync.ok?'OneDrive-Sync: ':'OneDrive-Sync-Problem: ');
|
||||
syncDot.title=prefix+lastSync.message;
|
||||
}
|
||||
function mkSyncDot(){
|
||||
var d=document.createElement('div');
|
||||
@@ -436,12 +437,37 @@ const profilesJs = `(function(){
|
||||
paintSyncDot();
|
||||
return d;
|
||||
}
|
||||
// Short-lived banner for a sync conflict -- a colour-only dot is too easy to
|
||||
// miss for "you might have lost an edit just now".
|
||||
var conflictToast=null,conflictToastTimer=null;
|
||||
function showConflictToast(msg){
|
||||
if(!conflictToast){
|
||||
conflictToast=document.createElement('div');
|
||||
conflictToast.id='mystart-sync-toast';
|
||||
conflictToast.style.cssText='position:fixed;top:44px;left:6px;max-width:360px;padding:8px 12px;'+
|
||||
'border-radius:8px;background:rgba(60,30,90,.92);color:#f0e6ff;font-size:12px;line-height:1.4;'+
|
||||
'box-shadow:0 2px 10px rgba(0,0,0,.4);z-index:2147483647;cursor:pointer;';
|
||||
conflictToast.title='Zum Ausblenden klicken';
|
||||
conflictToast.addEventListener('click',hideConflictToast);
|
||||
document.body.appendChild(conflictToast);
|
||||
}
|
||||
conflictToast.textContent='⚠ '+msg;
|
||||
conflictToast.style.display='';
|
||||
if(conflictToastTimer)clearTimeout(conflictToastTimer);
|
||||
conflictToastTimer=setTimeout(hideConflictToast,10000);
|
||||
}
|
||||
function hideConflictToast(){
|
||||
if(conflictToast)conflictToast.style.display='none';
|
||||
if(conflictToastTimer){clearTimeout(conflictToastTimer);conflictToastTimer=null;}
|
||||
}
|
||||
if(window.chrome&&window.chrome.webview){
|
||||
window.chrome.webview.addEventListener('message',function(ev){
|
||||
var d=ev.data;
|
||||
if(!d||d.type!=='sync-status')return;
|
||||
var wasConflict=lastSync&&lastSync.conflict;
|
||||
lastSync=d;
|
||||
paintSyncDot();
|
||||
if(d.conflict&&!wasConflict)showConflictToast('Gleichzeitige Änderung erkannt — dein Stand wurde trotzdem gespeichert: '+d.message);
|
||||
});
|
||||
}
|
||||
var bar;
|
||||
@@ -503,7 +529,11 @@ const profilesJs = `(function(){
|
||||
new MutationObserver(function(){if(!document.getElementById('mystart-profiles'))add();}).observe(document.documentElement,{childList:true,subtree:true});
|
||||
})();`;
|
||||
|
||||
mainJs = trInfra + autostartJs + windowControlsJs + profilesJs + mainJs;
|
||||
// Local images/videos from this PC in the image/video URL fields (kept as its
|
||||
// own file: it is full of backslash regexes that are painful inside a template string).
|
||||
const localFilesJs = fs.readFileSync(path.join(__dirname, 'inject', 'local-files.js'), 'utf8');
|
||||
|
||||
mainJs = trInfra + autostartJs + windowControlsJs + profilesJs + localFilesJs + mainJs;
|
||||
|
||||
const trPatches = [
|
||||
// panel header (top-level AND sub, both go through element.header)
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Lokale Bilder/Videos von diesem PC in den Bild-/Video-Feldern von nightTab nutzen.
|
||||
//
|
||||
// Die Seite laeuft unter https://mystart.local; der Browser blockt von dort aus
|
||||
// file://-Adressen. Die App (Program.cs) liefert deshalb lokale Dateien unter
|
||||
// https://files.mystart.local/<laufwerk>/<pfad> (C:\a\b.jpg -> /c/a/b.jpg)
|
||||
// https://files.mystart.local/unc/<server>/<freigabe>/<pfad> (\\server\share\x.png)
|
||||
// aus. Dieses Skript wandelt eingetippte/eingefuegte Pfade in solche Adressen um
|
||||
// und haengt an die Bild-/Video-Felder einen "Lokale Datei waehlen"-Knopf.
|
||||
(function () {
|
||||
if (!(window.chrome && window.chrome.webview)) return;
|
||||
|
||||
var HOST = 'https://files.mystart.local/';
|
||||
var FIELD = /(image|video)-url$/; // theme-background-image-url, display-visual-image-url, ...
|
||||
|
||||
function encodeSegments(path) {
|
||||
return path.split(/[\\/]+/).filter(Boolean).map(encodeURIComponent).join('/');
|
||||
}
|
||||
|
||||
// Ein Pfad (auch "In Anfuehrungszeichen", wie "Als Pfad kopieren" ihn liefert,
|
||||
// oder file:///) -> Adresse der App. Alles andere -> null (bleibt unveraendert).
|
||||
function toUrl(line) {
|
||||
var s = line.trim();
|
||||
if (s.length > 1 && s.charAt(0) === '"' && s.charAt(s.length - 1) === '"') s = s.slice(1, -1).trim();
|
||||
if (/^file:\/\/\//i.test(s)) {
|
||||
s = s.replace(/^file:\/\/\//i, '');
|
||||
try { s = decodeURI(s); } catch (e) { /* unveraendert lassen */ }
|
||||
}
|
||||
if (/^[A-Za-z]:[\\/]/.test(s)) return HOST + s.charAt(0).toLowerCase() + '/' + encodeSegments(s.slice(3));
|
||||
if (/^\\\\[^\\]/.test(s)) return HOST + 'unc/' + encodeSegments(s.slice(2));
|
||||
return null;
|
||||
}
|
||||
|
||||
function isMediaField(el) {
|
||||
return el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') && FIELD.test(el.id || '');
|
||||
}
|
||||
|
||||
function setValue(el, value) {
|
||||
el.value = value;
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
|
||||
// Pfade in einem Feld (auch mehrzeilig, z. B. mehrere Hintergrundbilder) umschreiben.
|
||||
function fix(el) {
|
||||
if (!isMediaField(el) || !el.value) return;
|
||||
var changed = false;
|
||||
var out = el.value.split(/\r?\n/).map(function (l) {
|
||||
var u = toUrl(l);
|
||||
if (u === null) return l;
|
||||
changed = true;
|
||||
return u;
|
||||
});
|
||||
if (changed) setValue(el, out.join('\n'));
|
||||
}
|
||||
|
||||
// Erst beim Verlassen des Felds/Enter (change) bzw. nach dem Einfuegen umwandeln,
|
||||
// sonst wuerde schon "C:\" waehrend des Tippens umgeschrieben.
|
||||
document.addEventListener('change', function (ev) { fix(ev.target); }, true);
|
||||
document.addEventListener('paste', function (ev) {
|
||||
var t = ev.target;
|
||||
setTimeout(function () { fix(t); }, 0);
|
||||
}, true);
|
||||
|
||||
// --- "Lokale Datei waehlen"-Knopf ---
|
||||
var targets = {};
|
||||
var nextToken = 1;
|
||||
|
||||
window.chrome.webview.addEventListener('message', function (ev) {
|
||||
var d = ev.data;
|
||||
if (!d || d.type !== 'file-picked') return;
|
||||
var el = targets[d.token];
|
||||
delete targets[d.token];
|
||||
if (!el || !d.paths || !d.paths.length) return;
|
||||
var urls = d.paths.map(function (p) { return toUrl(p) || p; });
|
||||
var multi = /theme-background-image-url$/.test(el.id || '');
|
||||
var existing = el.value.trim();
|
||||
setValue(el, multi && existing ? existing + '\n' + urls.join('\n') : urls.join('\n'));
|
||||
});
|
||||
|
||||
function addButton(el) {
|
||||
if (el.getAttribute('data-mystart-pick')) return;
|
||||
el.setAttribute('data-mystart-pick', '1');
|
||||
var b = document.createElement('div');
|
||||
b.textContent = 'Lokale Datei wählen…';
|
||||
b.title = 'Bild/Video von diesem PC auswählen';
|
||||
b.style.cssText = 'display:inline-block;margin-top:6px;padding:5px 10px;border-radius:6px;' +
|
||||
'background:rgba(128,128,128,.25);cursor:pointer;user-select:none;font-size:12px;';
|
||||
b.onmouseenter = function () { b.style.background = 'rgba(128,128,128,.4)'; };
|
||||
b.onmouseleave = function () { b.style.background = 'rgba(128,128,128,.25)'; };
|
||||
b.addEventListener('click', function () {
|
||||
var token = nextToken++;
|
||||
targets[token] = el;
|
||||
try {
|
||||
window.chrome.webview.postMessage({
|
||||
type: 'pick-file',
|
||||
token: token,
|
||||
kind: /video-url$/.test(el.id) ? 'video' : 'image',
|
||||
multiple: /theme-background-image-url$/.test(el.id)
|
||||
});
|
||||
} catch (e) { /* ignore */ }
|
||||
});
|
||||
el.insertAdjacentElement('afterend', b);
|
||||
}
|
||||
|
||||
var SELECTOR = '#theme-background-image-url,#theme-background-video-url,#display-visual-image-url,' +
|
||||
'#background-image-url,#background-video-url';
|
||||
var scheduled = false;
|
||||
function scan() {
|
||||
scheduled = false;
|
||||
var list = document.querySelectorAll(SELECTOR);
|
||||
for (var i = 0; i < list.length; i++) addButton(list[i]);
|
||||
}
|
||||
new MutationObserver(function () {
|
||||
if (!scheduled) { scheduled = true; requestAnimationFrame(scan); }
|
||||
}).observe(document.documentElement, { childList: true, subtree: true });
|
||||
})();
|
||||
Reference in New Issue
Block a user