feat(aether): initial commit – self-hosted Cloud, Phasen 1–7
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA. Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb, Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming, Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office (Collabora/OnlyOffice). Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort) sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
31a027c93a
225 files changed
+38042
No files matched your search
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Textebene von PDF.js.
|
||||
*
|
||||
* Der Block unten ist **wörtlich** aus pdfjs-dist/web/pdf_viewer.css
|
||||
* übernommen, nur die Klasse heißt `.pdf-textLayer` statt `.textLayer`.
|
||||
*
|
||||
* Beim ersten Anlauf hatte ich ihn von Hand gekürzt – „wir zeigen nur Seiten
|
||||
* an, der Rest ist Werkzeugleiste". Dabei fielen drei Zeilen weg, die wie
|
||||
* Kleinkram aussahen und keiner waren:
|
||||
*
|
||||
* --min-font-size / --text-scale-factor / scale(var(--min-font-size-inv))
|
||||
*
|
||||
* PDF.js misst beim ersten Aufbau, wie klein der Browser Schrift überhaupt
|
||||
* noch darstellt (Chrome erzwingt eine Mindestgröße), setzt daraus
|
||||
* `--min-font-size` und rechnet sie mit der Skalierung wieder heraus. Ohne
|
||||
* dieses Paar wird jede kleine Schrift vom Browser aufgeblasen: Die
|
||||
* unsichtbaren Textstücke sitzen dann größer da als die gemalten Buchstaben
|
||||
* darunter – zu sehen erst beim **Markieren**, wo die Auswahl als grobe,
|
||||
* versetzte Kästen über dem Text lag statt auf ihm.
|
||||
*
|
||||
* Deshalb hier nichts mehr von Hand kürzen. Was die Datei mitbringt, gehört
|
||||
* zusammen; was wir zusätzlich wollen, steht darunter.
|
||||
*/
|
||||
|
||||
.pdf-textLayer{
|
||||
color-scheme:only light;
|
||||
|
||||
position:absolute;
|
||||
text-align:initial;
|
||||
inset:0;
|
||||
overflow:clip;
|
||||
opacity:1;
|
||||
line-height:1;
|
||||
letter-spacing:normal;
|
||||
word-spacing:normal;
|
||||
-webkit-text-size-adjust:none;
|
||||
-moz-text-size-adjust:none;
|
||||
text-size-adjust:none;
|
||||
forced-color-adjust:none;
|
||||
transform-origin:0 0;
|
||||
caret-color:CanvasText;
|
||||
z-index:0;
|
||||
|
||||
&.highlighting{
|
||||
touch-action:none;
|
||||
}
|
||||
|
||||
:is(span, br){
|
||||
color:transparent;
|
||||
position:absolute;
|
||||
white-space:pre;
|
||||
cursor:text;
|
||||
transform-origin:0% 0%;
|
||||
-webkit-user-select:text;
|
||||
-moz-user-select:text;
|
||||
user-select:text;
|
||||
}
|
||||
--min-font-size:1;
|
||||
--text-scale-factor:calc(var(--total-scale-factor) * var(--min-font-size));
|
||||
--min-font-size-inv:calc(1 / var(--min-font-size));
|
||||
|
||||
> :not(.markedContent),
|
||||
.markedContent span:not(.markedContent){
|
||||
z-index:1;
|
||||
|
||||
--font-height:0;
|
||||
font-size:calc(var(--text-scale-factor) * var(--font-height));
|
||||
|
||||
--scale-x:1;
|
||||
--rotate:0deg;
|
||||
transform:rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv));
|
||||
}
|
||||
|
||||
.markedContent{
|
||||
display:contents;
|
||||
}
|
||||
|
||||
span[role="img"]{
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
user-select:none;
|
||||
cursor:default;
|
||||
}
|
||||
|
||||
.highlight{
|
||||
--highlight-bg-color:rgb(180 0 170 / 0.25);
|
||||
--highlight-selected-bg-color:rgb(0 100 0 / 0.25);
|
||||
--highlight-backdrop-filter:none;
|
||||
--highlight-selected-backdrop-filter:none;
|
||||
|
||||
@media screen and (forced-colors: active){
|
||||
--highlight-bg-color:transparent;
|
||||
--highlight-selected-bg-color:transparent;
|
||||
--highlight-backdrop-filter:var(--hcm-highlight-filter);
|
||||
--highlight-selected-backdrop-filter:var(
|
||||
--hcm-highlight-selected-filter
|
||||
);
|
||||
}
|
||||
|
||||
margin:-1px;
|
||||
padding:1px;
|
||||
background-color:var(--highlight-bg-color);
|
||||
backdrop-filter:var(--highlight-backdrop-filter);
|
||||
border-radius:4px;
|
||||
|
||||
&.appended{
|
||||
position:initial;
|
||||
}
|
||||
|
||||
&.begin{
|
||||
border-radius:4px 0 0 4px;
|
||||
}
|
||||
|
||||
&.end{
|
||||
border-radius:0 4px 4px 0;
|
||||
}
|
||||
|
||||
&.middle{
|
||||
border-radius:0;
|
||||
}
|
||||
|
||||
&.selected{
|
||||
background-color:var(--highlight-selected-bg-color);
|
||||
backdrop-filter:var(--highlight-selected-backdrop-filter);
|
||||
scroll-margin-top:50px;
|
||||
}
|
||||
}
|
||||
|
||||
::-moz-selection{
|
||||
background:rgba(0 0 255 / 0.25);
|
||||
background:color-mix(in srgb, AccentColor, transparent 50%);
|
||||
color:transparent;
|
||||
}
|
||||
|
||||
::selection{
|
||||
background:rgba(0 0 255 / 0.25);
|
||||
background:color-mix(in srgb, AccentColor, transparent 50%);
|
||||
color:transparent;
|
||||
}
|
||||
|
||||
&.selectionRendering{
|
||||
::-moz-selection{
|
||||
background:transparent;
|
||||
color:transparent;
|
||||
}
|
||||
::selection{
|
||||
background:transparent;
|
||||
color:transparent;
|
||||
}
|
||||
}
|
||||
br::-moz-selection{
|
||||
background:transparent;
|
||||
}
|
||||
br::selection{
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
.endOfContent{
|
||||
display:block;
|
||||
position:absolute;
|
||||
inset:100% 0 0;
|
||||
z-index:0;
|
||||
cursor:default;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
user-select:none;
|
||||
}
|
||||
|
||||
&.selecting .endOfContent{
|
||||
top:0;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Fundstellen der Suche.
|
||||
*
|
||||
* Eine eigene Ebene über der Textebene, kein <mark> im Text. Warum, steht
|
||||
* ausführlich in PdfViewer.tsx bei `paint` – kurz: Der Grund eines <mark>
|
||||
* ragte oben aus der Textebene heraus, und die schneidet ab (`overflow:
|
||||
* clip`). Das sah man als angeschnittene Streifen. Diese Ebene schneidet
|
||||
* nichts ab; die Kästen sind dieselben, die der Browser beim Markieren mit
|
||||
* der Maus malt.
|
||||
*
|
||||
* `pointer-events: none`, damit die Ebene das Markieren mit der Maus nicht
|
||||
* abfängt – darunter liegt die Schrift, die man auswählen können soll.
|
||||
*
|
||||
* Die Farbe bleibt durchsichtig: Der lesbare Text steht auf dem gemalten Bild
|
||||
* *darunter*. Mit 0,95 Deckkraft lag ein satter Block über dem Wort, und
|
||||
* ausgerechnet das gesuchte Wort war das einzige, das man nicht mehr lesen
|
||||
* konnte. Der Betrachter von PDF.js selbst arbeitet aus demselben Grund mit
|
||||
* 0,25.
|
||||
*/
|
||||
.pdf-hitLayer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.pdf-hit {
|
||||
position: absolute;
|
||||
background: rgba(245, 158, 11, 0.3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.pdf-hit.pdf-hit-current {
|
||||
background: rgba(245, 158, 11, 0.55);
|
||||
/* Der aktuelle Treffer bekommt zusätzlich eine Kante statt noch mehr Farbe –
|
||||
Deckkraft würde wieder das Wort verschlucken. */
|
||||
box-shadow: 0 0 0 1px rgba(180, 83, 9, 0.75);
|
||||
}
|
||||
Reference in new issue
Block a user