Asketisches Netz: Prototyp-Flohmärkte für unterwegs und js

Screenshot


Hallo allerseits, ich möchte das Ergebnis meiner Gedanken darüber teilen, was eine moderne Webanwendung sein kann. Als Beispiel sollten Sie ein Bulletin Board für Comics entwerfen. In gewisser Weise ist das betreffende Produkt für ein Publikum von Geeks und Sympathisanten konzipiert, wodurch Sie die Freiheit in der Benutzeroberfläche zeigen können. In der technischen Komponente ist dagegen die Liebe zum Detail erforderlich.


In Wahrheit verstehe ich nichts in den Comics, aber ich liebe Flohmärkte, besonders im Forum-Format, die in der Null beliebt waren. Daher ist die Annahme (möglicherweise falsch), aus der die folgenden Schlussfolgerungen hervorgehen, nur eine - die Hauptart der Interaktion mit der Anwendung ist das Anzeigen, die sekundären veröffentlichen Ankündigungen und Diskussionen.


Unser Ziel wird es sein, eine einfache Anwendung ohne zu erstellen technisches Know-how zusätzliche Pfeifen im Einklang mit der modernen Realität. Die Hauptanforderungen, die ich erfüllen möchte, sind:


  1. Serverseite:


    a) Führt die Funktionen zum Speichern, Validieren und Senden von Benutzerdaten an den Client aus
    b) Die oben genannten Vorgänge verbrauchen eine akzeptable Menge an Ressourcen (Zeit, einschließlich)
    c) Die Anwendung und die Daten sind vor gängigen Angriffsmethoden geschützt
    d) Es verfügt über eine einfache API für Clients von Drittanbietern und für die Interaktion zwischen Servern
    e) Plattformübergreifende, einfache Bereitstellung


  2. Client-Seite:


    a) Bietet die erforderlichen Funktionen zum Erstellen und Konsumieren von Inhalten
    b) Die Benutzeroberfläche ist praktisch für den regelmäßigen Gebrauch, der minimale Pfad zu einer Aktion, die maximale Datenmenge pro Bildschirm
    c) Außerhalb der Kommunikation mit dem Server sind alle in dieser Situation verfügbaren Funktionen verfügbar
    d) Die Schnittstelle zeigt die aktuelle Version des Status und des Inhalts an, ohne neu zu starten und zu warten
    d) Ein Neustart der Anwendung hat keinen Einfluss auf ihren Status
    f) Verwenden Sie nach Möglichkeit DOM-Elemente und JS-Code erneut
    g) Wir werden zur Laufzeit keine Bibliotheken und Frameworks von Drittanbietern verwenden
    h) Das Layout ist semantisch für Barrierefreiheit, Parser usw.
    i) Die Navigation des Hauptinhalts ist über die URL und die Tastatur möglich



Meiner Meinung nach erfüllen logische Anforderungen und modernste Anwendungen in gewissem Maße diese Bedingungen. Mal sehen, was mit uns passiert (Link zu Quelle und Demo am Ende des Beitrags).


Warnungen:
  • Ich möchte mich bei den unbekannten Autoren der in der Demo ohne Erlaubnis verwendeten Bilder sowie bei Gösse G., Prozorovskaya B. D. und dem Verlag "Library of Florence Pavlenkov" für die Verwendung von Auszügen aus dem Werk "Siddhartha" entschuldigen.
  • Der Autor ist kein echter Programmierer. Ich empfehle nicht, den Code oder die in diesem Projekt verwendeten Techniken zu verwenden, wenn Sie nicht wissen, was Sie tun.
  • Ich entschuldige mich für den Stil des Codes, er hätte lesbarer und offensichtlicher geschrieben werden können, aber das macht keinen Spaß. Ein Projekt für die Seele und für einen Freund, wie es heißt.
  • Ich entschuldige mich auch für die Alphabetisierungsrate, insbesondere im englischen Text. Jahre sprechen ab Mai Hart.
  • Die Leistung des vorgestellten Prototyps wurde in [Chrom 70; Linux x86_64; 1366x768] werde ich Benutzern anderer Plattformen und Geräte für Fehlermeldungen äußerst dankbar sein.
  • Dies ist ein Prototyp und ein vorgeschlagenes Diskussionsthema - Ansätze und Prinzipien, ich bitte darum, dass alle Kritikpunkte an der Umsetzung und der ästhetischen Seite von Argumenten begleitet werden.

Server


Die Sprache für den Server ist Golang. Eine einfache, schnelle Sprache mit einer exzellenten Standardbibliothek und Dokumentation ... etwas nervig. Die anfängliche Wahl fiel auf elixir / erlang, aber da ich go (relativ) bereits kannte, wurde beschlossen, es nicht zu komplizieren (und die notwendigen Pakete waren nur für go).


Die Verwendung von Web-Frameworks in der Go-Community wird nicht empfohlen (zu Recht, es lohnt sich zuzugeben), wir wählen einen Kompromiss und verwenden das Labstack- / Echo-Mikroframework , wodurch der Routineaufwand reduziert wird und meiner Meinung nach nicht viel an Leistung verloren geht.


Wir verwenden tidwall / buntdb als Datenbank. Erstens ist die integrierte Lösung bequemer und reduziert die Overhead-Kosten, und zweitens In-Memory + Schlüssel / Wert - modisch, stilvoll Schnell und kein Cache erforderlich. Wir speichern und geben Daten in JSON und validieren sie nur bei Änderungen.


Beim i3 der zweiten Generation zeigt der integrierte Logger die Ausführungszeit für verschiedene Anforderungen von 0,5 bis 10 ms an. Das Ausführen von wrk auf demselben Computer zeigt auch ausreichende Ergebnisse für unsere Zwecke:


➜ comico git:(master) wrk -t2 -c500 -d60s http://localhost:9001/pub/mtimes Running 1m test @ http://localhost:9001/pub/mtimes 2 threads and 500 connections Thread Stats Avg Stdev Max +/- Stdev Latency 20.74ms 16.68ms 236.16ms 72.69% Req/Sec 13.19k 627.43 15.62k 73.58% 1575522 requests in 1.00m, 449.26MB read Requests/sec: 26231.85 Transfer/sec: 7.48MB 

 ➜ comico git:(master) wrk -t2 -c500 -d60s http://localhost:9001/pub/goods Running 1m test @ http://localhost:9001/pub/goods 2 threads and 500 connections Thread Stats Avg Stdev Max +/- Stdev Latency 61.79ms 65.96ms 643.73ms 86.48% Req/Sec 5.26k 705.24 7.88k 70.31% 628215 requests in 1.00m, 8.44GB read Requests/sec: 10454.44 Transfer/sec: 143.89MB 

Projektstruktur


Das comico / model- Paket ist in drei Dateien unterteilt:
model.go - enthält eine Beschreibung der Datentypen und allgemeinen Funktionen: Erstellung / Aktualisierung (buntdb unterscheidet nicht zwischen diesen Vorgängen und wir prüfen manuell, ob ein Datensatz vorhanden ist), Validierung, Löschung, Abrufen eines Datensatzes und Abrufen einer Liste;
rules.go - enthält Validierungsregeln für einen bestimmten Typ und eine bestimmte Protokollierungsfunktion.
files.go - arbeite mit Bildern.
Der Mtimes-Typ speichert Daten zur letzten Änderung der verbleibenden Typen in der Datenbank und informiert so den Client darüber, welche Daten sich geändert haben.


Das comico / bd- Paket enthält allgemeine Funktionen für die Interaktion mit der Datenbank: Erstellen, Löschen, Auswählen usw. Buntdb speichert alle Änderungen an einer Datei (in unserem Fall einmal pro Sekunde) im Textformat, was in einigen Situationen praktisch ist. Die Datenbankdatei wird nicht bearbeitet, Änderungen bei Erfolg der Transaktion werden am Ende hinzugefügt. Alle meine Versuche, die Integrität der Daten zu verletzen, waren erfolglos. Im schlimmsten Fall gehen Änderungen in der letzten Sekunde verloren.
In unserer Implementierung entspricht jeder Typ einer separaten Datenbank in einer separaten Datei (mit Ausnahme von Protokollen, die ausschließlich im Speicher gespeichert und beim Neustart auf Null zurückgesetzt werden). Dies ist hauptsächlich auf die Bequemlichkeit der Sicherung und Verwaltung zurückzuführen, ein kleines Plus - eine offene Transaktion zum Bearbeiten blockiert den Zugriff auf nur einen Datentyp.
Dieses Paket kann einfach durch ein ähnliches ersetzt werden, indem eine andere Datenbank verwendet wird, z. B. SQL. Dazu reicht es aus, folgende Funktionen zu implementieren:


 func Delete(db byte, key string) error func Exist(db byte, key string) bool func Insert(db byte, key, val string) error func ReadAll(db byte, pattern string) (str string, err error) func ReadOne(db byte, key string) (str string, err error) func Renew(db byte, key string) (err error, newId string) 

Das comico / cnst-Paket enthält einige Konstanten, die in allen Paketen erforderlich sind (Datentypen, Aktionstypen, Benutzertypen). Darüber hinaus enthält dieses Paket alle für Menschen lesbaren Nachrichten, mit denen unser Server auf die Außenwelt reagiert.


Das Comico / Server- Paket enthält Routing-Informationen. Außerdem sind nur ein paar Zeilen (dank der Echo-Entwickler), die Autorisierung mithilfe von JWT, CORS, CSP-Headern, Logger, statischer Verteilung, gzip, ACME-Autozertifikat usw. konfiguriert.


API-Einstiegspunkte


URLDatenBeschreibung
get / pub / (Waren | Beiträge | Benutzer | cmnts | Dateien)- -Abrufen einer Reihe relevanter Ankündigungen, Beiträge, Benutzer, Kommentare und Dateien
get / pub / mtimes- -Abrufen der letzten Änderungszeit für jeden Datentyp
post / pub / login{id *: login, pass *: password}Gibt das JWT-Token und seine Dauer zurück
Post / Pub / Pass{id *, pass *}Erstellt einen neuen Benutzer, wenn die Daten korrekt sind
put / api / pass{id *, pass *}Passwort aktualisieren
post | put / api / ware{id *, auth *, title *, type *, price *, text *, images: [], Table: {key: value}}Anzeige erstellen / aktualisieren
post | put / api / posts{id *, auth *, title *, type *, text *}Forenbeitrag erstellen / aktualisieren
post | put / api / users{id *, Titel, Typ, Status, Schreiber: [], ignoriert: [], Tabelle: {Schlüssel: Wert}}Benutzer erstellen / aktualisieren
post / api / cmnts{id *, auth *, owner *, type *, to, text *}Kommentarerstellung
delete / api / (Waren | Beiträge | Benutzer | cmnts) / [id]- -Löscht einen Eintrag mit der ID
get / api / activity- -Aktualisiert die letzte Lesezeit eingehender Kommentare für den aktuellen Benutzer
get / api / (abonnieren | ignorieren) / [tag]- -Fügt dem Benutzer in der Liste der Abonnements / Ignorieren (falls vorhanden) Tags hinzu oder entfernt sie
post / api / upload / (Waren | Benutzer)mehrteilig (Name, Datei)Lädt Fotoanzeigen / Benutzer-Avatar hoch

* - Pflichtfelder
api - erfordert autorisierung, pub - nr


Bei einer Get-Anforderung, die nicht mit der oben genannten übereinstimmt, sucht der Server im Verzeichnis nach einer Datei für statische Daten (z. B. / img / * - images, /index.html - the client).
Jeder API-Punkt gibt bei Erfolg einen 200-Antwortcode, bei einem Fehler 400 oder 404 und bei Bedarf eine kurze Nachricht zurück.
Zugriffsrechte sind einfach: Das Erstellen eines Eintrags steht einem autorisierten Benutzer zur Verfügung, das Bearbeiten für den Autor und den Moderator. Der Administrator kann Moderatoren bearbeiten und ernennen.
Die API ist mit der einfachsten Anti-Vandalismus-Funktion ausgestattet: Aktionen werden zusammen mit der Benutzer-ID und der IP-Adresse protokolliert. Bei häufigem Zugriff wird ein Fehler zurückgegeben, in dem Sie aufgefordert werden, etwas zu warten (nützlich gegen das Erraten von Kennwörtern).


Kunde


Ich mag das Konzept des reaktiven Web'a. Ich denke, dass die meisten modernen Websites / Anwendungen entweder im Rahmen dieses Konzepts oder vollständig statisch erstellt werden sollten. Andererseits kann eine einfache Site mit Megabyte JS-Code nur drücken. Meiner Meinung nach kann dieses (und nicht nur) Problem von Svelte gelöst werden. Dieses Framework (oder besser gesagt die Sprache zum Erstellen reaktiver Schnittstellen) ist Vue in der erforderlichen Funktionalität nicht unterlegen, hat jedoch einen unbestreitbaren Vorteil: Komponenten werden in Vanilla JS kompiliert, wodurch sowohl die Größe des Bundles als auch die Belastung der virtuellen Maschine (bundle.min.js.gz) reduziert werden Unser Flohmarkt ist nach heutigen Maßstäben bescheiden (24 KB). Details finden Sie in der offiziellen Dokumentation .


Wir wählen den SvelteJS-Flohmarkt für die Kundenseite des Flohmarktes. Wir wünschen Rich Harris alles Gute und die weitere Entwicklung des Projekts!


PS Ich möchte niemanden beleidigen. Ich bin sicher, dass jeder Spezialist und jedes Projekt seine eigenen Werkzeuge hat.


Kunde / Daten


URL


Wir verwenden für die Navigation. Wir simulieren kein mehrseitiges Dokument, sondern verwenden Hash-Seiten mit Abfrageparametern. Für Übergänge können Sie das übliche <a> ohne js verwenden.


Abschnitte entsprechen Datentypen: / # Waren , / # Beiträge , / # Benutzer .
Parameter :? Id = record_id ,? Page = page_number ,? Search = search_query .


Einige Beispiele:


  • / # posts? id = 1542309643 & page = 999 & search = {auth: anon} - Abschnittsbeiträge, Beitrags-ID - 1542309643 , Kommentarseite - 999 , Suchabfrage - {auth: anon}
  • / # Waren? Seite = 2 & Suche = Siddhartha - Abschnitt Waren , Abschnitt Seite - 2 , Suchabfrage - Siddhartha
  • / # Waren? search = wer {Schlüssel: Wert} t - Abschnitt Waren , Suchabfrage - besteht aus der Suche nach der wert- Teilzeichenfolge in der Kopfzeile oder im Text der Anzeige und dem Teilzeichenfolgenwert in der Schlüsseleigenschaft des tabellarischen Teils der Anzeige
  • / # Waren? search = {Modell: 100, Anzeige: 256} - Ich denke, hier ist analog alles klar

Die Funktionen des Parsens und der URL-Generierung in unserer Implementierung sehen folgendermaßen aus:


 window.addEventListener('hashchange', function() { const hash = location.hash.slice(1).split('?'), result = {} if (!!hash[1]) hash[1].split('&').forEach(str => { str = str.split('=') if (!!str[0] && !!str[1]) result[decodeURI(str[0]).toLowerCase()] = decodeURI(str[1]).toLowerCase() }) result.type = hash[0] || 'goods' store.set({ hash: result }) }) function goto({ type, id, page, search }) { const { hash } = store.get(), args = arguments[0], query = [] new Array('id', 'page', 'search').forEach(key => { const value = args[key] !== undefined ? args[key] : hash[key] || null if (value !== null) query.push(key + '=' + value) }) location.hash = (type || hash.type || 'goods') + (!!query.length ? '?' + query.join('&') : '') } 

API


Um Daten mit dem Server auszutauschen, verwenden wir die Fetch-API. Um aktualisierte Datensätze in kurzen Abständen herunterzuladen , senden wir eine Anfrage an / pub / mtimes . Wenn der Zeitpunkt der letzten Änderung für einen Typ vom lokalen abweicht, laden wir eine Liste dieses Typs. Ja, es war möglich, Benachrichtigungen über Updates über SSE oder WebSockets und inkrementelles Laden zu implementieren, aber in diesem Fall können wir darauf verzichten. Was haben wir bekommen:


 async function GET(type) { const response = await fetch(location.origin + '/pub/' + type) .catch(() => ({ ok: false })) if (type === 'mtimes') store.set({ online: response.ok }) return response.ok ? await response.json() : [] } async function checkUpdate(type, mtimes, updates = {}) { const local = store.get()._mtimes, net = mtimes || await GET('mtimes') if (!net[type] || local[type] === net[type]) return const value = updates['_' + type] = await GET(type) local[type] = net[type]; updates._mtimes = local if (!!value && !!value.sort) store.set(updates) } async function checkUpdates() { setTimeout(() => checkUpdates(), 30000) const mtimes = await store.GET('mtimes') new Array('users', 'goods', 'posts', 'cmnts', 'files') .forEach(type => checkUpdate(type, mtimes)) } 

Zum Filtern und Paginieren verwenden wir die berechneten Eigenschaften von Svelte basierend auf Navigationsdaten. Die Richtung der berechneten Werte lautet: Elemente (Arrays von Datensätzen, die vom Server stammen) => ignoreItems (gefilterte Datensätze basierend auf der Ignorierliste des aktuellen Benutzers) => scribedItems (filtert Datensätze gemäß der Liste der Abonnements, wenn dieser Modus aktiviert ist) => curItem und curItems (berechnet die aktuellen Datensätze je nach Abschnitt) => filteredItems (filtert Datensätze abhängig von der Suchabfrage, wenn es nur einen Datensatz gibt - filtert Kommentare darauf) => maxPage (berechnet die Anzahl der Seiten basierend auf 12 Datensätzen / Kommentaren pro Seite) => pagedItem (gibt das endgültige Array mit zurück Beiträge / Kommentare basierend auf der aktuellen Seitenzahl).


Kommentare und Bilder ( Kommentare und _Bilder ) werden separat berechnet und nach Typ und Eigentümerdatensatz gruppiert.


Berechnungen erfolgen automatisch und nur wenn sich die zugehörigen Daten ändern, befinden sich die Zwischendaten ständig im Speicher. In dieser Hinsicht ziehen wir eine unangenehme Schlussfolgerung - für eine große Menge an Informationen und / oder deren häufige Aktualisierung kann eine große Menge an Ressourcen aufgewendet werden.


Cache


Entsprechend der Entscheidung, eine Offline-Anwendung zu erstellen, implementieren wir die Speicherung von Datensätzen und einigen Aspekten des Status in localStorage, Bilddateien in CacheStorage. Die Arbeit mit localStorage ist äußerst einfach. Wir sind uns einig, dass Eigenschaften mit dem Präfix "_" beim Neustart automatisch gespeichert und wiederhergestellt werden, wenn sie geändert werden. Dann könnte unsere Lösung so aussehen:


 store.on('state', ({ changed, current }) => { Object.keys(changed).forEach(prop => { if (!prop.indexOf('_')) localStorage.setItem(prop, JSON.stringify(current[prop])) }) }) function loadState(state = {}) { for (let i = 0; i < localStorage.length; i++) { const prop = localStorage.key(i) const value = JSON.parse(localStorage.getItem(prop) || 'null') if (!!value && !prop.indexOf('_')) state[prop] = value } store.set(state) } 

Dateien sind etwas komplizierter. Zunächst verwenden wir die Liste aller relevanten Dateien (mit Erstellungszeit), die vom Server stammen. Wenn wir diese Liste aktualisieren, vergleichen wir sie mit den alten Werten, legen die neuen Dateien in CacheStorage ab und löschen die veralteten von dort:


 async function cacheImages(newFiles) { const oldFiles = JSON.parse(localStorage.getItem('_files') || '[]') const cache = await caches.open('comico') oldFiles.forEach(file => { if (!~newFiles.indexOf(file)) { const [ id, type ] = file.split(':') cache.delete(`/img/${type}_${id}_sm.jpg`) }}) newFiles.forEach(file => { if (!~oldFiles.indexOf(file)) { const [ id, type ] = file.split(':'), src = `/img/${type}_${id}_sm.jpg` cache.add(new Request(src, { cache: 'no-cache' })) }}) } 

Anschließend müssen Sie das Abrufverhalten neu definieren, damit die Datei aus CacheStorage entnommen wird, ohne eine Verbindung zum Server herzustellen. Dazu müssen Sie ServiceWorker verwenden. Gleichzeitig konfigurieren wir andere Dateien, die zwischengespeichert werden sollen, damit sie außerhalb der Kommunikation mit dem Server funktionieren:


 const CACHE = 'comico', FILES = [ '/', '/bundle.css', '/bundle.js' ] self.addEventListener('install', (e) => { e.waitUntil(caches.open(CACHE).then(cache => cache.addAll(FILES)) .then(() => self.skipWaiting())) }) self.addEventListener('fetch', (e) => { const r = e.request if (r.method !== 'GET' || !!~r.url.indexOf('/pub/') || !!~r.url.indexOf('/api/')) return if (!!~r.url.lastIndexOf('_sm.jpg') && e.request.cache !== 'no-cache') return e.respondWith(fromCache(r)) e.respondWith(toCache(r)) }) async function fromCache(request) { return await (await caches.open(CACHE)).match(request) || new Response(null, { status: 404 }) } async function toCache(request) { const response = await fetch(request).catch(() => fromCache(request)) if (!!response && response.ok) (await caches.open(CACHE)).put(request, response.clone()) return response } 

Es sieht etwas ungeschickt aus, erfüllt aber seine Funktionen.


Client / Schnittstelle


Komponentenstruktur:
index.html | main.js
== header.html - enthält ein Logo, eine Statusleiste, ein Hauptmenü, ein unteres Navigationsmenü und ein Formular zum Einreichen von Kommentaren
== beiseite.html - ist ein Container für alle modalen Komponenten
==== goodForm.html - Formular zum Hinzufügen und Bearbeiten einer Anzeige
==== userForm.html - Formular des aktuellen Benutzers bearbeiten
====== tableForm.html - Ein Fragment des Formulars zur Eingabe von Tabellendaten
==== postForm.html - Formular für den Forumsbeitrag
==== login.html - Anmelde- / Registrierungsformular
==== activity.html - Zeigt Kommentare an, die an den aktuellen Benutzer gerichtet sind
==== goodImage.html - Zeigen Sie die Haupt- und Zusatzfotoanzeigen an
== main.html - Container für den Hauptinhalt
==== goods.html - Liste oder einzelne Ankündigungskarten
==== users.html - Gleiches gilt für Benutzer
==== posts.html - Ich denke, es ist klar
==== cmnts.html - Liste der Kommentare zum aktuellen Beitrag
====== cmntsPager.html - Paginierung für Kommentare


  • In jeder Komponente versuchen wir, die Anzahl der HTML-Tags zu minimieren.
  • Wir verwenden Klassen nur als Indikator für den Zustand.
  • Wir nehmen ähnliche Funktionen wie der Store heraus (schlanke Store-Eigenschaften und -Methoden können direkt von Komponenten verwendet werden, indem ihnen das Präfix '$' hinzugefügt wird).
  • Die meisten Funktionen erwarten ein Benutzerereignis oder eine Änderung bestimmter Eigenschaften, bearbeiten die Statusdaten, speichern das Ergebnis ihrer Arbeit im Status und beenden es. Somit wird eine geringe Kohärenz und Erweiterbarkeit des Codes erreicht.
  • Für die scheinbare Geschwindigkeit von Übergängen und anderen UI-Ereignissen trennen wir Manipulationen mit Daten im Hintergrund und Aktionen im Zusammenhang mit der Schnittstelle so weit wie möglich. Dabei wird das aktuelle Berechnungsergebnis verwendet und bei Bedarf neu erstellt. Der Rest wird freundlicherweise vom Framework ausgeführt.
  • Die Daten des auszufüllenden Formulars werden für jede Eingabe in localStorage gespeichert, um deren Verlust zu verhindern.
  • In allen Komponenten verwenden wir den unveränderlichen Modus, in dem das Eigenschaftsobjekt nur dann als geändert betrachtet wird, wenn ein neuer Link empfangen wird, unabhängig von der Änderung in den Feldern, wodurch unsere Anwendungen ein wenig beschleunigt werden, wenn auch aufgrund einer geringfügigen Erhöhung der Codemenge.

Kunde / Management


Zur Steuerung über die Tastatur verwenden wir die folgenden Kombinationen:
Alt + s / Alt + a - schaltet die Seite der Datensätze vorwärts / rückwärts, während ein Datensatz die Seite der Kommentare wechselt.
Alt + w / Alt + q - wechselt zum nächsten / vorherigen Datensatz (falls vorhanden), arbeitet im Listenmodus, in einem einzelnen Datensatz und in der Bildansicht
Alt + x / Alt + z - scrollt die Seite nach unten / oben. Schaltet in der Bildansicht Bilder vorwärts / rückwärts um
Escape - schließt das modale Fenster, wenn es geöffnet ist, kehrt zur Liste zurück, wenn ein einzelner Eintrag geöffnet ist, bricht die Suchabfrage im Listenmodus ab
Alt + c - konzentriert sich je nach aktuellem Modus auf das Such- oder Kommentarfeld
Alt + V - Aktivieren / Deaktivieren des Fotoansichtsmodus für eine einzelne Anzeige
Alt + r - öffnet / schließt die Liste der eingehenden Kommentare für einen autorisierten Benutzer
Alt + t - Schaltet helle / dunkle Themen um
Alt + g - Liste der Anzeigen
Alt + u - Benutzer
Alt + p - Forum
Ich weiß, dass diese Kombinationen in vielen Browsern vom Browser selbst verwendet werden, aber für mein Chrome konnte ich mir nichts Bequemeres einfallen lassen. Ich freue mich über Ihre Vorschläge.


Neben der Tastatur können Sie natürlich auch die Browserkonsole verwenden. Beispiel: store.goBack () , store.nextPage () , store.prevPage () , store.nextItem () , store.prevItem () , store.search (stringValue) , store.checkUpdate ('Waren' || ' Benutzer '||' Beiträge '||' Dateien '||' cmnts ') - tun, was der Name impliziert; store.get (). Kommentare und store.get () ._ images - gibt gruppierte Dateien und Kommentare zurück; store.get (). ignoreItems und store.get (). scribedItems sind Listen von Datensätzen, die Sie ignorieren und verfolgen. Eine vollständige Liste aller Zwischen- und berechneten Daten finden Sie unter store.get () . Ich glaube nicht, dass jemand dies ernsthaft braucht, aber das Filtern von Datensätzen nach Benutzer und das Löschen von Datensätzen schien mir von der Konsole aus recht praktisch zu sein.


Fazit


Hier können Sie Ihre Bekanntschaft mit dem Projekt beenden, weitere Details finden Sie im Quellcode. Als Ergebnis haben wir eine ziemlich schnelle und kompakte Anwendung erhalten, die in den meisten Prüfern für Validatoren, Sicherheit, Geschwindigkeit, Verfügbarkeit usw. gute Ergebnisse ohne gezielte Optimierung zeigt.
Ich würde gerne die Meinung der Community erfahren, wie gerechtfertigt die Ansätze zur Organisation der im Prototyp verwendeten Anwendungen sind, welche Fallstricke es geben könnte und was Sie grundlegend anders implementieren würden.
Quellcode, Beispielinstallationsanweisungen und Demo hier (bitte zerstören im Rahmen des Strafgesetzbuches zu testen).


Nachtrag. Ein wenig kaufmännisch zum Schluss. Sagen Sie mir, ist es mit einem solchen Level wirklich möglich, für Geld zu programmieren? Wenn nicht, worauf Sie zuerst achten sollten, wenn ja, sagen Sie mir, wo sie jetzt nach interessanten Arbeiten an einem ähnlichen Stapel suchen. Vielen Dank.


Nachtrag. Ein bisschen mehr über Geld und Arbeit. Wie gefällt Ihnen diese Idee: Angenommen, eine Person ist bereit, für jedes Gehalt an einem für sie interessanten Projekt zu arbeiten. Daten zu Aufgaben und deren Bezahlung werden jedoch öffentlich verfügbar sein (Verfügbarkeit und ein Code zur Beurteilung der Leistungsqualität sind wünschenswert), wenn die Bezahlung deutlich unter dem Markt liegt, die Wettbewerber des Arbeitgebers kann viel Geld für die Erfüllung ihrer Aufgaben anbieten, wenn es höher ist - viele Künstler können ihre Dienste zu einem niedrigeren Preis anbieten. Wird ein solches System in einigen Situationen den Markt (IT) optimaler und fairer ausbalancieren?

Source: https://habr.com/ru/post/de435908/


All Articles