feat(webshell): neues Library-Product ManaWebShell (v0.6.0)
WKWebView-Huelle fuer Hybrid-Apps (Web-Lese-Surfaces + native Submit/Widget/ShareExt). Extrahiert aus den fast-byte-identischen `WebShell/`-Ordnern in seepuls-native und zitare-native (~900 LOC, davon ~700 LOC Duplikat). Audit 2026-05-17 V2. Neu (public API): - `WebShellView` — WKWebView-Wrapper mit Progress-Bar, Pull-to- Refresh (iOS), Fehler-Snackbar, External-Link-Delegation. Universal (iOS + macOS) - `WebShellConfig` — Host-Whitelist mit Wildcard-Support (`"*.mana.how"`), User-Agent, Theme-Hints, User-Scripts - `WebTarget` — URL + monoton wachsender reloadToken - `WebNavState` — @Observable, @MainActor, reaktiver Nav-State - `WebShellCoordinator` — WKNavigationDelegate + WKUIDelegate - `WebShellScripts` — Helfer fuer `preferDarkScheme`, `syncDarkMode(localStorageKey:)`, `hideElements(selectors:tagName:)` Logging unter Subsystem `ev.mana.webshell` (App-OSLog bleibt eigen). Tests: 6 neue Tests gegen `WebShellConfig.isAllowed` (Wildcards, Negativ-Cases). 50/50 grün insgesamt (6 ManaWebShell + 44 ManaAuthUI). Doku: `mana/docs/playbooks/HYBRID_NATIVE_APP.md` (Schwester-Repo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e284240f3c
commit
8f4d4b0c03
9 changed files with 689 additions and 0 deletions
119
Sources/ManaWebShell/WebShellScripts.swift
Normal file
119
Sources/ManaWebShell/WebShellScripts.swift
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import Foundation
|
||||
import WebKit
|
||||
|
||||
/// Vor-gefertigte `WKUserScript`-Helfer für ``WebShellView``. Apps
|
||||
/// pickern, was sie brauchen, und reichen das Ergebnis als
|
||||
/// `config.userScripts` durch.
|
||||
///
|
||||
/// `WKUserScript` ist MainActor-isolated; deshalb sind die Factory-
|
||||
/// Methoden hier ebenfalls MainActor. Aufrufer leben sowieso auf Main
|
||||
/// (SwiftUI `makeUIView`/`makeNSView` sind MainActor).
|
||||
@MainActor
|
||||
public enum WebShellScripts {
|
||||
/// Erzwingt Dark-Color-Scheme im WebView, indem ein `<meta
|
||||
/// name="color-scheme" content="dark">` injiziert und `.dark` an
|
||||
/// `<html>` gehängt wird. Sinnvoll für Web-Apps, die nur Dark-
|
||||
/// Styles haben (Seepuls) oder bei denen die App das Light/Dark
|
||||
/// hart festlegt.
|
||||
public static let preferDarkScheme: WKUserScript = .init(
|
||||
source: """
|
||||
(function() {
|
||||
var meta = document.querySelector('meta[name="color-scheme"]');
|
||||
if (!meta) {
|
||||
meta = document.createElement('meta');
|
||||
meta.setAttribute('name', 'color-scheme');
|
||||
(document.head || document.documentElement).appendChild(meta);
|
||||
}
|
||||
meta.setAttribute('content', 'dark');
|
||||
var html = document.documentElement;
|
||||
if (html) html.classList.add('dark');
|
||||
})();
|
||||
""",
|
||||
injectionTime: .atDocumentStart,
|
||||
forMainFrameOnly: true
|
||||
)
|
||||
|
||||
/// Synct den System-Dark-Mode in den WebView via
|
||||
/// `matchMedia('(prefers-color-scheme: dark)')`. Setzt eine
|
||||
/// `.dark`-Klasse auf `<html>` und optional einen `localStorage`-
|
||||
/// Key, an dem das Web-Theme hängt. Listener für Live-Switch
|
||||
/// während die Page offen ist.
|
||||
///
|
||||
/// - Parameter localStorageKey: Key, an dem das Web seinen Theme-
|
||||
/// State liest. `nil` falls Web nur auf `<html>.dark` reagiert.
|
||||
public static func syncDarkMode(localStorageKey: String? = nil) -> WKUserScript {
|
||||
let setStorage: String
|
||||
if let key = localStorageKey {
|
||||
let escaped = key.replacingOccurrences(of: "'", with: "\\'")
|
||||
setStorage = """
|
||||
try {
|
||||
if (isDark) localStorage.setItem('\(escaped)', 'dark');
|
||||
else localStorage.removeItem('\(escaped)');
|
||||
} catch (e) {}
|
||||
"""
|
||||
} else {
|
||||
setStorage = ""
|
||||
}
|
||||
let source = """
|
||||
(function() {
|
||||
function apply(isDark) {
|
||||
\(setStorage)
|
||||
var html = document.documentElement;
|
||||
if (!html) return;
|
||||
if (isDark) html.classList.add('dark');
|
||||
else html.classList.remove('dark');
|
||||
}
|
||||
var mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
apply(mq.matches);
|
||||
if (mq.addEventListener) {
|
||||
mq.addEventListener('change', function(e) { apply(e.matches); });
|
||||
}
|
||||
})();
|
||||
"""
|
||||
return WKUserScript(
|
||||
source: source,
|
||||
injectionTime: .atDocumentStart,
|
||||
forMainFrameOnly: true
|
||||
)
|
||||
}
|
||||
|
||||
/// Versteckt eine Top-Nav-Komponente per CSS, damit eine native
|
||||
/// TabBar nicht doppelt rendert. Mehrere Selektoren werden
|
||||
/// gestapelt (mit `,`-Group), damit ein Markup-Refactor in
|
||||
/// Web-Land das Hide nicht still bricht.
|
||||
///
|
||||
/// Konvention für Selektor-Kaskaden:
|
||||
/// 1. `nav[data-app-nav]` / `header[data-app-nav]` — explizites
|
||||
/// Attribut, falls Web es markieren will (greift sofort)
|
||||
/// 2. strukturell (`body header:has(a.brand)` o.ä.) — heutige
|
||||
/// Realität
|
||||
/// 3. positionell (`body > nav:first-of-type`) — Fallback
|
||||
///
|
||||
/// - Parameter selectors: CSS-Selektoren, die `display: none
|
||||
/// !important` bekommen. Werden mit `,` gejoint.
|
||||
/// - Parameter tagName: Wert für das `data-mana-webshell`-
|
||||
/// Attribut auf dem Style-Tag (debugging, source inspection).
|
||||
public static func hideElements(
|
||||
selectors: [String],
|
||||
tagName: String = "hide"
|
||||
) -> WKUserScript {
|
||||
let joined = selectors.joined(separator: ",\n")
|
||||
let escapedTag = tagName.replacingOccurrences(of: "'", with: "\\'")
|
||||
let source = """
|
||||
(function() {
|
||||
var css = `\(joined) {
|
||||
display: none !important;
|
||||
}`;
|
||||
var style = document.createElement('style');
|
||||
style.setAttribute('data-mana-webshell', '\(escapedTag)');
|
||||
style.textContent = css;
|
||||
(document.head || document.documentElement).appendChild(style);
|
||||
})();
|
||||
"""
|
||||
return WKUserScript(
|
||||
source: source,
|
||||
injectionTime: .atDocumentStart,
|
||||
forMainFrameOnly: true
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue