@upstart.gg/vite-plugins 0.1.60 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/page-meta.js +533 -0
  2. package/dist/page-meta.js.map +1 -0
  3. package/dist/site-meta.d.ts +28 -0
  4. package/dist/site-meta.d.ts.map +1 -0
  5. package/dist/site-meta.js +207 -0
  6. package/dist/site-meta.js.map +1 -0
  7. package/dist/upstart-editor-api.d.ts +135 -1
  8. package/dist/upstart-editor-api.d.ts.map +1 -1
  9. package/dist/upstart-editor-api.js +756 -1
  10. package/dist/upstart-editor-api.js.map +1 -1
  11. package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
  12. package/dist/vite-plugin-upstart-attrs.js +130 -10
  13. package/dist/vite-plugin-upstart-attrs.js.map +1 -1
  14. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
  15. package/dist/vite-plugin-upstart-editor/runtime/index.js +35 -0
  16. package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
  17. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
  18. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +139 -14
  19. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
  20. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +3 -0
  21. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
  22. package/package.json +8 -3
  23. package/src/page-meta.ts +678 -0
  24. package/src/site-meta.ts +226 -0
  25. package/src/tests/site-meta.test.ts +158 -0
  26. package/src/tests/upstart-editor-api-page-meta.test.ts +635 -0
  27. package/src/tests/vite-plugin-upstart-attrs.test.ts +224 -13
  28. package/src/upstart-editor-api.ts +941 -0
  29. package/src/vite-plugin-upstart-attrs.ts +253 -14
  30. package/src/vite-plugin-upstart-editor/runtime/index.ts +38 -0
  31. package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +141 -14
  32. package/src/vite-plugin-upstart-editor/runtime/types.ts +2 -0
@@ -1701,7 +1701,7 @@ describe("upstart-editor-vite-plugin", () => {
1701
1701
  expect(result).toContain('data-upstart-editable-text="true"');
1702
1702
  });
1703
1703
 
1704
- test("should NOT be editable when Trans is mixed with static text", () => {
1704
+ test("should be editable with a prefix when Trans is mixed with static text", () => {
1705
1705
  const code = `
1706
1706
  import { Trans } from "react-i18next";
1707
1707
  export default function App() {
@@ -1709,10 +1709,11 @@ describe("upstart-editor-vite-plugin", () => {
1709
1709
  }
1710
1710
  `;
1711
1711
  const result = transform(code);
1712
- // "Prefix: " is non-whitespace JSXText alongside Transmixed NOT editable
1713
- // Saving would incorrectly include "Prefix: " in the translation value
1714
- expect(result).not.toContain('data-upstart-editable-text="true"');
1715
- expect(result).not.toContain("data-upstart-i18n");
1712
+ // "Prefix: " only surrounds a single i18n node exported as a static affix so
1713
+ // the runtime editor keeps it out of the saved translation value
1714
+ expect(result).toContain('data-upstart-editable-text="true"');
1715
+ expect(result).toContain('data-upstart-i18n="translation:key"');
1716
+ expect(result).toContain('data-upstart-i18n-prefix="Prefix: "');
1716
1717
  });
1717
1718
 
1718
1719
  test("should promote <Trans> from inside && expression to parent", () => {
@@ -1735,8 +1736,8 @@ describe("upstart-editor-vite-plugin", () => {
1735
1736
  }
1736
1737
  `;
1737
1738
  const result = transform(code);
1738
- // Both keys should be comma-separated on the parent
1739
- expect(result).toContain('data-upstart-i18n="app:welcome,app:login"');
1739
+ // Only one branch renders, so the key is resolved at runtime from the condition
1740
+ expect(result).toContain('data-upstart-i18n={isLoggedIn ? "app:welcome" : "app:login"}');
1740
1741
  expect(result).toContain('data-upstart-editable-text="true"');
1741
1742
  });
1742
1743
 
@@ -1842,9 +1843,11 @@ describe("upstart-editor-vite-plugin", () => {
1842
1843
  }
1843
1844
  `;
1844
1845
  const result = transform(code);
1845
- // Both branches are i18n — no non-i18n text produced
1846
+ // Both branches are i18n — no non-i18n text produced, key resolved at runtime
1846
1847
  expect(result).toContain('data-upstart-editable-text="true"');
1847
- expect(result).toContain('data-upstart-i18n="translation:role.admin,translation:role.user"');
1848
+ expect(result).toContain(
1849
+ 'data-upstart-i18n={isAdmin ? "translation:role.admin" : "translation:role.user"}',
1850
+ );
1848
1851
  });
1849
1852
 
1850
1853
  test("should still be editable when only whitespace {' '} separates Trans from nothing", () => {
@@ -1860,6 +1863,211 @@ describe("upstart-editor-vite-plugin", () => {
1860
1863
  expect(result).toContain('data-upstart-i18n="translation:label"');
1861
1864
  });
1862
1865
  });
1866
+
1867
+ describe("i18n with static text affixes", () => {
1868
+ test("should be editable with a suffix when a bare asterisk follows Trans", () => {
1869
+ const code = `
1870
+ import { Trans } from "react-i18next";
1871
+ export default function App() {
1872
+ return (
1873
+ <label className="label">
1874
+ <Trans i18nKey="contact.form.phone" /> *
1875
+ </label>
1876
+ );
1877
+ }
1878
+ `;
1879
+ const result = transform(code);
1880
+ expect(result).toContain('data-upstart-editable-text="true"');
1881
+ expect(result).toContain('data-upstart-editable-text-mode="plain"');
1882
+ expect(result).toContain('data-upstart-i18n="translation:contact.form.phone"');
1883
+ expect(result).toContain('data-upstart-i18n-suffix=" *"');
1884
+ expect(result).not.toContain("data-upstart-i18n-prefix");
1885
+ });
1886
+
1887
+ test("should be editable with a suffix when the asterisk is wrapped in a static span", () => {
1888
+ const code = `
1889
+ import { Trans } from "react-i18next";
1890
+ export default function App() {
1891
+ return (
1892
+ <label className="label">
1893
+ <Trans i18nKey="contact.form.name" /> <span aria-hidden="true">*</span>
1894
+ </label>
1895
+ );
1896
+ }
1897
+ `;
1898
+ const result = transform(code);
1899
+ expect(result).toContain('data-upstart-editable-text="true"');
1900
+ expect(result).toContain('data-upstart-i18n="translation:contact.form.name"');
1901
+ expect(result).toContain('data-upstart-i18n-suffix=" *"');
1902
+ });
1903
+
1904
+ test("should capture a static prefix before Trans", () => {
1905
+ const code = `
1906
+ import { Trans } from "react-i18next";
1907
+ export default function App() {
1908
+ return <label>* <Trans i18nKey="form.city" /></label>;
1909
+ }
1910
+ `;
1911
+ const result = transform(code);
1912
+ expect(result).toContain('data-upstart-editable-text="true"');
1913
+ expect(result).toContain('data-upstart-i18n-prefix="* "');
1914
+ expect(result).not.toContain("data-upstart-i18n-suffix");
1915
+ });
1916
+
1917
+ test("should escape quotes in affixes", () => {
1918
+ const code = `
1919
+ import { Trans } from "react-i18next";
1920
+ export default function App() {
1921
+ return <label><Trans i18nKey="form.size" /> (")</label>;
1922
+ }
1923
+ `;
1924
+ const result = transform(code);
1925
+ expect(result).toContain('data-upstart-i18n-suffix=" (&quot;)"');
1926
+ });
1927
+
1928
+ test("should NOT emit affixes when there are two i18n sources around static text", () => {
1929
+ const code = `
1930
+ import { Trans } from "react-i18next";
1931
+ export default function App() {
1932
+ return <p><Trans i18nKey="a" /> / <Trans i18nKey="b" /></p>;
1933
+ }
1934
+ `;
1935
+ const result = transform(code);
1936
+ expect(result).not.toContain('data-upstart-editable-text="true"');
1937
+ expect(result).not.toContain("data-upstart-i18n-suffix");
1938
+ });
1939
+
1940
+ test("should NOT emit affixes when a dynamic expression sits next to Trans", () => {
1941
+ const code = `
1942
+ import { Trans } from "react-i18next";
1943
+ export default function App() {
1944
+ return <label><Trans i18nKey="form.count" /> ({count})</label>;
1945
+ }
1946
+ `;
1947
+ const result = transform(code);
1948
+ expect(result).not.toContain('data-upstart-editable-text="true"');
1949
+ expect(result).not.toContain("data-upstart-i18n-suffix");
1950
+ });
1951
+
1952
+ test("should NOT emit affixes when a sibling element is not purely static", () => {
1953
+ const code = `
1954
+ import { Trans } from "react-i18next";
1955
+ export default function App() {
1956
+ return <label><Trans i18nKey="form.city" /> <span>{hint}</span></label>;
1957
+ }
1958
+ `;
1959
+ const result = transform(code);
1960
+ expect(result).not.toContain('data-upstart-editable-text="true"');
1961
+ });
1962
+
1963
+ test("should NOT emit affixes when the element has no resolvable single key", () => {
1964
+ const code = `
1965
+ import { Trans } from "react-i18next";
1966
+ export default function App() {
1967
+ return <p><Trans i18nKey="a" /><Trans i18nKey="b" /></p>;
1968
+ }
1969
+ `;
1970
+ const result = transform(code);
1971
+ // Two translations render at once → no key can be resolved
1972
+ expect(result).not.toContain("data-upstart-i18n");
1973
+ expect(result).not.toContain('data-upstart-editable-text="true"');
1974
+ });
1975
+
1976
+ test("should emit affixes around a t() call too", () => {
1977
+ const code = `
1978
+ import { useTranslation } from "react-i18next";
1979
+ export default function App() {
1980
+ const { t } = useTranslation();
1981
+ return <label>{t("form.email")} *</label>;
1982
+ }
1983
+ `;
1984
+ const result = transform(code);
1985
+ expect(result).toContain('data-upstart-editable-text="true"');
1986
+ expect(result).toContain('data-upstart-i18n="translation:form.email"');
1987
+ expect(result).toContain('data-upstart-i18n-suffix=" *"');
1988
+ });
1989
+ });
1990
+ });
1991
+
1992
+ describe("Runtime-resolved i18n key (single key per element)", () => {
1993
+ test("should resolve the key from the condition for a submit button", () => {
1994
+ const code = `
1995
+ import { Trans } from "react-i18next";
1996
+ export default function App({ isSubmitting }) {
1997
+ return (
1998
+ <button type="submit" disabled={isSubmitting}>
1999
+ {isSubmitting ? <Trans i18nKey="form.submitting" /> : <Trans i18nKey="form.submit" />}
2000
+ </button>
2001
+ );
2002
+ }
2003
+ `;
2004
+ const result = transform(code);
2005
+ expect(result).toContain('data-upstart-editable-text="true"');
2006
+ expect(result).toContain(
2007
+ 'data-upstart-i18n={isSubmitting ? "translation:form.submitting" : "translation:form.submit"}',
2008
+ );
2009
+ // Never a comma-separated list — the runtime cannot parse those
2010
+ expect(result).not.toMatch(/data-upstart-i18n="[^"]*,/);
2011
+ });
2012
+
2013
+ test("should keep a member-expression condition intact", () => {
2014
+ const code = `
2015
+ import { Trans } from "react-i18next";
2016
+ export default function App({ nav }) {
2017
+ return <span>{nav.state === "idle" ? <Trans i18nKey="a" /> : <Trans i18nKey="b" />}</span>;
2018
+ }
2019
+ `;
2020
+ const result = transform(code);
2021
+ expect(result).toContain(
2022
+ 'data-upstart-i18n={nav.state === "idle" ? "translation:a" : "translation:b"}',
2023
+ );
2024
+ });
2025
+
2026
+ test("should mix a runtime-resolved key with a static suffix", () => {
2027
+ const code = `
2028
+ import { Trans } from "react-i18next";
2029
+ export default function App({ isSubmitting }) {
2030
+ return <label>{isSubmitting ? <Trans i18nKey="a" /> : <Trans i18nKey="b" />} *</label>;
2031
+ }
2032
+ `;
2033
+ const result = transform(code);
2034
+ expect(result).toContain('data-upstart-i18n-suffix=" *"');
2035
+ expect(result).toContain('data-upstart-i18n={isSubmitting ? "translation:a" : "translation:b"}');
2036
+ });
2037
+
2038
+ test("should NOT be editable when a Trans sits next to an i18n ternary", () => {
2039
+ const code = `
2040
+ import { Trans } from "react-i18next";
2041
+ export default function App({ flag }) {
2042
+ return <p><Trans i18nKey="lead" />{flag ? <Trans i18nKey="a" /> : <Trans i18nKey="b" />}</p>;
2043
+ }
2044
+ `;
2045
+ const result = transform(code);
2046
+ expect(result).not.toContain("data-upstart-i18n");
2047
+ expect(result).not.toContain('data-upstart-editable-text="true"');
2048
+ });
2049
+
2050
+ test("should NOT be editable when a ternary branch holds two translations", () => {
2051
+ const code = `
2052
+ import { Trans } from "react-i18next";
2053
+ export default function App({ flag }) {
2054
+ return <p>{flag ? <><Trans i18nKey="a" /><Trans i18nKey="b" /></> : <Trans i18nKey="c" />}</p>;
2055
+ }
2056
+ `;
2057
+ const result = transform(code);
2058
+ expect(result).not.toContain("data-upstart-i18n=");
2059
+ });
2060
+
2061
+ test("should still emit a plain static key for a single translation", () => {
2062
+ const code = `
2063
+ import { Trans } from "react-i18next";
2064
+ export default function App() {
2065
+ return <h1><Trans i18nKey="site.title" /></h1>;
2066
+ }
2067
+ `;
2068
+ const result = transform(code);
2069
+ expect(result).toContain('data-upstart-i18n="translation:site.title"');
2070
+ });
1863
2071
  });
1864
2072
 
1865
2073
  describe("useTranslation t() Function Detection", () => {
@@ -1921,7 +2129,9 @@ describe("upstart-editor-vite-plugin", () => {
1921
2129
  }
1922
2130
  `;
1923
2131
  const result = transform(code);
1924
- expect(result).toContain('data-upstart-i18n="translation:active,translation:inactive"');
2132
+ expect(result).toContain(
2133
+ 'data-upstart-i18n={isActive ? "translation:active" : "translation:inactive"}',
2134
+ );
1925
2135
  });
1926
2136
 
1927
2137
  test("should detect t() in logical expression", () => {
@@ -1961,7 +2171,7 @@ describe("upstart-editor-vite-plugin", () => {
1961
2171
  expect(result).toContain('data-upstart-i18n="translation:features.title"');
1962
2172
  });
1963
2173
 
1964
- test("should combine Trans and t() keys in same parent", () => {
2174
+ test("should NOT be editable when a t() call and a Trans render side by side", () => {
1965
2175
  const code = `
1966
2176
  import { useTranslation } from "react-i18next";
1967
2177
  import { Trans } from "react-i18next";
@@ -1971,8 +2181,9 @@ describe("upstart-editor-vite-plugin", () => {
1971
2181
  }
1972
2182
  `;
1973
2183
  const result = transform(code);
1974
- expect(result).toContain('data-upstart-i18n="app:suffix,app:prefix"');
1975
- expect(result).toContain('data-upstart-editable-text="true"');
2184
+ // Both translations render at once: an inline edit could not be routed to a key
2185
+ expect(result).not.toContain("data-upstart-i18n");
2186
+ expect(result).not.toContain('data-upstart-editable-text="true"');
1976
2187
  });
1977
2188
 
1978
2189
  test("should detect t() inside .map() callback on child element", () => {