mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-08 04:29:25 -06:00
fix: add old Safari versions compatibility (#1058)
* fix: add Safari compatibility polyfills and workarounds * fix: for new safaris * fix --------- Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
This commit is contained in:
@@ -57,7 +57,7 @@ export const fixMetadataJson = (metadataJson: string): string => {
|
||||
|
||||
/* eslint-disable-next-line no-useless-escape */
|
||||
.replace(/\"/g, '___ESCAPED_QUOTE___') // Temporarily replace empty strings
|
||||
.replace(/(?<=:\s*)""(?=\s*[,}])/g, '___EMPTY___') // Temporarily replace empty strings
|
||||
.replace(/(:\s*)""(?=\s*[,}])/g, '$1___EMPTY___') // Temporarily replace empty strings (Safari-compatible)
|
||||
.replace(/""/g, '"') // Replace remaining double quotes
|
||||
.replace(/___ESCAPED_QUOTE___/g, '"') // Restore empty strings
|
||||
.replace(/___EMPTY___/g, '""') // Restore empty strings
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Polyfills must be imported first for Safari compatibility
|
||||
import './polyfills';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useTheme } from '@/hooks/use-theme';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { getIsOldSafari } from '@/safari-compat';
|
||||
|
||||
export interface NoteNodeProps extends NodeProps {
|
||||
data: {
|
||||
@@ -193,7 +194,9 @@ export const NoteNode: React.FC<NoteNodeProps> = ({
|
||||
<div className="h-full overflow-auto break-words text-sm leading-relaxed text-gray-700 dark:text-gray-300">
|
||||
{note.content ? (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
remarkPlugins={
|
||||
getIsOldSafari() ? [] : [remarkGfm]
|
||||
}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
components={{
|
||||
h1: (props) => (
|
||||
|
||||
55
src/polyfills.ts
Normal file
55
src/polyfills.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Polyfills for Safari compatibility
|
||||
* Safari 15.3 and earlier don't support these modern JavaScript features
|
||||
*/
|
||||
|
||||
// Polyfill for structuredClone (Safari < 15.4)
|
||||
if (typeof globalThis.structuredClone === 'undefined') {
|
||||
globalThis.structuredClone = <T>(obj: T): T => {
|
||||
// For simple cases, use JSON parse/stringify
|
||||
// This doesn't handle all edge cases (like circular refs, Map, Set, etc.)
|
||||
// but works for most common use cases
|
||||
if (obj === undefined) return undefined as T;
|
||||
if (obj === null) return null as T;
|
||||
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
} catch {
|
||||
// Fallback for objects that can't be JSON serialized
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Polyfill for Array.prototype.at (Safari < 15.4)
|
||||
if (!Array.prototype.at) {
|
||||
Array.prototype.at = function <T>(this: T[], index: number): T | undefined {
|
||||
const length = this.length;
|
||||
const relativeIndex = index >= 0 ? index : length + index;
|
||||
if (relativeIndex < 0 || relativeIndex >= length) {
|
||||
return undefined;
|
||||
}
|
||||
return this[relativeIndex];
|
||||
};
|
||||
}
|
||||
|
||||
// Polyfill for String.prototype.at (Safari < 15.4)
|
||||
if (!String.prototype.at) {
|
||||
String.prototype.at = function (index: number): string | undefined {
|
||||
const length = this.length;
|
||||
const relativeIndex = index >= 0 ? index : length + index;
|
||||
if (relativeIndex < 0 || relativeIndex >= length) {
|
||||
return undefined;
|
||||
}
|
||||
return this[relativeIndex];
|
||||
};
|
||||
}
|
||||
|
||||
// Polyfill for Object.hasOwn (Safari < 15.4)
|
||||
if (!Object.hasOwn) {
|
||||
Object.hasOwn = function (obj: object, prop: PropertyKey): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(obj, prop);
|
||||
};
|
||||
}
|
||||
|
||||
export {};
|
||||
48
src/safari-compat.ts
Normal file
48
src/safari-compat.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Safari compatibility utilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* Detect if the current browser is Safari
|
||||
*/
|
||||
const isSafari = (): boolean => {
|
||||
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ua = navigator.userAgent;
|
||||
// Safari but not Chrome (Chrome UA also contains "Safari")
|
||||
return (
|
||||
ua.includes('Safari') &&
|
||||
!ua.includes('Chrome') &&
|
||||
!ua.includes('Chromium')
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the browser supports the regex features used by remark-gfm.
|
||||
* remark-gfm uses unicode property escapes (\p{P}) which older Safari
|
||||
* misinterprets as named capture groups, throwing "invalid group specifier name"
|
||||
*/
|
||||
const hasRemarkGfmRegexIssue = (): boolean => {
|
||||
try {
|
||||
// Test the pattern remark-gfm uses in transformGfmAutolinkLiterals
|
||||
new RegExp('(?<=\\p{P})a', 'u');
|
||||
return false; // Regex works fine
|
||||
} catch {
|
||||
return true; // Regex throws error
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if remarkGfm should be disabled.
|
||||
* Only disables for Safari browsers that have the regex issue.
|
||||
*/
|
||||
let shouldDisableCached: boolean | null = null;
|
||||
|
||||
export const getIsOldSafari = (): boolean => {
|
||||
if (shouldDisableCached === null) {
|
||||
shouldDisableCached = isSafari() && hasRemarkGfmRegexIssue();
|
||||
}
|
||||
return shouldDisableCached;
|
||||
};
|
||||
Reference in New Issue
Block a user