fix: Fix native themes not working on external editors (#2957) [skip e2e]

This commit is contained in:
Aman Harwara 2025-11-06 22:00:35 +05:30 committed by GitHub
parent cffb516b6e
commit 60e92c7035
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 1 deletions

View file

@ -38,6 +38,9 @@ import {
DocumentDirectoryPath,
DownloadDirectoryPath,
exists,
MainBundlePath,
readFile,
readFileAssets,
unlink,
writeFile,
} from 'react-native-fs'
@ -508,6 +511,15 @@ export class MobileDevice implements MobileDeviceInterface {
}
}
async getNativeThemeCSS(identifier: string): Promise<string | undefined> {
let path = `Web.bundle/src/web-src/components/assets/${identifier}/index.css`
if (Platform.OS === 'ios') {
path = `${MainBundlePath}/${path}`
}
const content = Platform.OS === 'android' ? readFileAssets(path) : readFile(path)
return content
}
async previewFile(base64: string, filename: string): Promise<boolean> {
const tempLocation = await this.downloadBase64AsFile(base64, filename, true)

View file

@ -24,6 +24,7 @@ export interface MobileDeviceInterface extends DeviceInterface {
handleThemeSchemeChange(isDark: boolean, bgColor: string): void
shareBase64AsFile(base64: string, filename: string): Promise<void>
downloadBase64AsFile(base64: string, filename: string, saveInTempLocation?: boolean): Promise<string | undefined>
getNativeThemeCSS(identifier: string): Promise<string | undefined>
previewFile(base64: string, filename: string): Promise<boolean>
exitApp(confirm?: boolean): void

View file

@ -30,7 +30,7 @@ import {
NativeFeatureIdentifier,
GetDeprecatedEditors,
} from '@standardnotes/features'
import { Copy, removeFromArray, sleep, isNotUndefined, LoggerInterface } from '@standardnotes/utils'
import { Copy, removeFromArray, sleep, isNotUndefined, LoggerInterface, blobToBase64 } from '@standardnotes/utils'
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
import {
AbstractService,
@ -90,6 +90,8 @@ export class ComponentManager
this.items,
)
private nativeThemesAsBase64: Record<string, string> = {}
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
@ -109,6 +111,7 @@ export class ComponentManager
this.addSyncedComponentItemObserver()
this.registerMobileNativeComponentUrls()
this.registerDeprecatedEditorUrlsForAndroid()
void this.fetchNativeThemesOnMobile()
this.eventDisposers.push(
preferences.addEventObserver((event) => {
@ -295,6 +298,29 @@ export class ComponentManager
}
}
/**
* Gets all the native themes' CSS and stores them as `data:text/css;base64,...` URLs.
*/
private async fetchNativeThemesOnMobile(): Promise<void> {
if (!isMobileDevice(this.device)) {
return
}
try {
for await (const theme of GetNativeThemes()) {
const css = await this.device.getNativeThemeCSS(theme.identifier)
if (css) {
const blob = new Blob([css], { type: 'text/css' })
const base64 = await blobToBase64(blob)
this.nativeThemesAsBase64[theme.identifier] = base64
}
}
} catch (error) {
console.error(error)
} finally {
this.postActiveThemesToAllViewers()
}
}
private registerDeprecatedEditorUrlsForAndroid(): void {
if (!isMobileDevice(this.device)) {
return
@ -367,7 +393,19 @@ export class ComponentManager
public urlsForActiveThemes(): string[] {
const themes = this.getActiveThemes()
const urls = []
const isMobile = isMobileDevice(this.device)
for (const theme of themes) {
if (isMobile && theme.isNativeFeature) {
/**
* Since native themes on mobile are stored in the app bundle and accessed as `file://` URLs,
* external editors cannot access them. To solve this, we store base64 encoded versions of the themes and send those to the editor instead of a file URL.
*/
const base64 = this.nativeThemesAsBase64[theme.featureIdentifier]
if (base64) {
urls.push(base64)
continue
}
}
const url = this.urlForFeature(theme)
if (url) {
urls.push(url)

View file

@ -699,3 +699,17 @@ export function spaceSeparatedStrings(...strings: string[]): string {
export function pluralize(count: number, singular: string, plural: string): string {
return count === 1 ? singular : plural
}
export function blobToBase64(blob: Blob): Promise<string> {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => {
if (reader.result && typeof reader.result === 'string') {
resolve(reader.result)
} else {
reject()
}
}
reader.readAsDataURL(blob)
})
}