eyeling 1.24.19 → 1.24.23

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.
package/HANDBOOK.md CHANGED
@@ -3760,11 +3760,11 @@ When a user does want a portable link, the **Copy share link** button creates on
3760
3760
  - edited programs are shared with a compact compressed `?state=...` payload,
3761
3761
  - default option values are omitted from that payload to keep links small.
3762
3762
 
3763
- If a generated compact share link is still very long, the playground reveals a **Create TinyURL** option. The threshold is intentionally conservative, and the shortener handoff is explicit rather than automatic: after the user chooses that option, the browser uses a TinyURL API token stored locally in that browser to create the short link and copy it to the clipboard. If no token is provided, or if the API request fails, the playground falls back to opening TinyURL and copying the long compact share link so it can be pasted manually. This avoids silently sending encoded editor content to a third-party service while still making the account-backed TinyURL workflow one click after setup.
3763
+ If a generated embedded-state link is still very long, the playground reveals **Create Gist share**. That option asks for a GitHub token with gist permission, stores the compact playground state as a secret Gist JSON file, and copies a small `?stateurl=...` playground link that fetches the state file client-side. The token is stored only in that browser's `localStorage`, the Gist is created by a POST request with `referrerPolicy: "no-referrer"`, and the shared playground URL no longer contains the large encoded program.
3764
3764
 
3765
3765
  This keeps everyday use pleasant while preserving the important tutorial and issue-reporting workflow: a link can still capture the imported resource, the local editable overlay, background-knowledge mode, proof-comments mode, and HTTPS-dereferencing mode.
3766
3766
 
3767
- For compatibility, older `?edit=`, `?program=`, `?url=`, compact `?state=`, and hash-based links are still accepted when opened. The old `/demo` entry point is also kept as a redirect to the canonical `/playground` page.
3767
+ For compatibility, older `?edit=`, `?program=`, `?url=`, compact `?state=`, `?stateurl=`, and hash-based links are accepted when opened. The old `/demo` entry point is also kept as a redirect to the canonical `/playground` page.
3768
3768
 
3769
3769
  ### I.6 What the playground is good for
3770
3770
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.24.19",
3
+ "version": "1.24.23",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -722,7 +722,8 @@ async function main() {
722
722
  renderedTabSelected: renderedTab ? renderedTab.getAttribute('aria-selected') === 'true' : false,
723
723
  sourceTabSelected: sourceTab ? sourceTab.getAttribute('aria-selected') === 'true' : false,
724
724
  shareStatus: document.getElementById('share-status') ? String(document.getElementById('share-status').textContent || '') : '',
725
- shortenerHidden: document.getElementById('open-shortener-btn') ? !!document.getElementById('open-shortener-btn').hidden : true,
725
+ gistShareHidden: document.getElementById('create-gist-share-btn') ? !!document.getElementById('create-gist-share-btn').hidden : true,
726
+ gistShareText: document.getElementById('create-gist-share-btn') ? String(document.getElementById('create-gist-share-btn').textContent || '').trim() : '',
726
727
  backgroundStatus: document.getElementById('background-status') ? String(document.getElementById('background-status').textContent || '') : '',
727
728
  href: String(window.location.href || ''),
728
729
  highlighted,
@@ -783,21 +784,33 @@ async function main() {
783
784
  return {
784
785
  url,
785
786
  length: url.length,
786
- threshold: window.__eyelingPlaygroundShareUrlShortenerThreshold,
787
- needsShortener: window.__eyelingPlaygroundShouldOfferShortener(url),
788
- shortenerUrl: window.__eyelingPlaygroundMakeShareUrlShortenerUrl(url),
787
+ threshold: window.__eyelingPlaygroundGistShareThreshold,
788
+ needsGistShare: window.__eyelingPlaygroundShouldOfferGistShare(url),
789
+ hasEmbeddedState: window.__eyelingPlaygroundShareUrlHasEmbeddedState(url),
790
+ stateUrlShare: window.__eyelingPlaygroundMakeShareUrlFromStateUrl('https://gist.githubusercontent.com/user/id/raw/eyeling-playground-state.json'),
789
791
  };
790
792
  })()`);
791
793
  }
792
794
 
793
- async function createTinyUrlWithStubInPage(longUrl, token, response) {
794
- const payload = JSON.stringify({ longUrl: String(longUrl), token: String(token), response });
795
+ async function createGistBackedShareUrlWithStubInPage(token, response) {
796
+ const payload = JSON.stringify({ token: String(token), response });
795
797
  return await evalInPage(`(async () => {
796
798
  const args = ${payload};
797
799
  const originalFetch = window.fetch;
798
800
  let seen = null;
799
801
  window.fetch = async (url, options) => {
800
- seen = { url: String(url || ''), options: options || null };
802
+ options = options || {};
803
+ seen = {
804
+ url: String(url || ''),
805
+ options: {
806
+ method: options.method,
807
+ headers: options.headers,
808
+ body: options.body,
809
+ cache: options.cache,
810
+ referrerPolicy: options.referrerPolicy,
811
+ hasSignal: !!options.signal,
812
+ },
813
+ };
801
814
  return {
802
815
  ok: true,
803
816
  status: 200,
@@ -805,8 +818,15 @@ async function main() {
805
818
  };
806
819
  };
807
820
  try {
808
- const tinyUrl = await window.__eyelingPlaygroundCreateTinyUrl(args.longUrl, args.token);
809
- return { tinyUrl, seen };
821
+ const state = {
822
+ edit: (window.__cmStubsById && window.__cmStubsById['n3-editor']) ? window.__cmStubsById['n3-editor'].getValue() : '',
823
+ url: '',
824
+ loadbg: false,
825
+ proofcomments: false,
826
+ httpsderef: true,
827
+ };
828
+ const shareUrl = await window.__eyelingPlaygroundCreateGistBackedShareUrl(state, args.token);
829
+ return { shareUrl, seen };
810
830
  } finally {
811
831
  window.fetch = originalFetch;
812
832
  }
@@ -959,11 +979,11 @@ ${JSON.stringify(last, null, 2)}`);
959
979
  assert.match(compactShareUrl, /[?&]state=/, 'Expected an on-demand compact state parameter');
960
980
  assert.doesNotMatch(compactShareUrl, /[?&](?:edit|program)=/, 'Expected share link to avoid raw edit/program params');
961
981
  assert.ok(compactShareUrl.length < playgroundUrl.length + encodeURIComponent(outputStringProgram).length, 'Expected compact share URL to be shorter than raw editor URL');
962
- assert.equal(renderedAgain.shortenerHidden, true, 'Expected ordinary compact share links to keep the shortener option hidden');
982
+ assert.equal(renderedAgain.gistShareHidden, true, 'Expected ordinary compact share links to keep the Gist share option hidden');
963
983
  endTest();
964
984
 
965
- // 6) Very large edited programs should offer a URL shortener handoff instead of only a huge link.
966
- beginTest('playground offers a URL shortener option for oversized share links');
985
+ // 6) Very large edited programs should offer a Gist-backed share option instead of only a huge link.
986
+ beginTest('playground offers a Gist-backed option for oversized state links');
967
987
  const longShareProgram = Array.from({ length: 1400 }, (_, i) => {
968
988
  const n = String(i).padStart(4, '0');
969
989
  const token = ((i * 2654435761) >>> 0).toString(36).padStart(7, '0');
@@ -972,17 +992,27 @@ ${JSON.stringify(last, null, 2)}`);
972
992
  await setProgram(longShareProgram);
973
993
  const longShare = await makeShareUrlDiagnosticsInPage();
974
994
  assert.ok(longShare.length > longShare.threshold, `Expected test share URL to exceed threshold (${longShare.length} <= ${longShare.threshold})`);
975
- assert.equal(longShare.needsShortener, true, 'Expected oversized share URL to request a shortener option');
976
- assert.match(longShare.shortenerUrl, /^https:\/\/tinyurl\.com\/app\?url=/, 'Expected TinyURL app fallback handoff');
977
- assert.ok(longShare.shortenerUrl.includes(encodeURIComponent(longShare.url).slice(0, 40)), 'Expected shortener URL to carry the generated share URL');
978
- const tinyUrlCreated = await createTinyUrlWithStubInPage(longShare.url, 'test-token-123', {
979
- data: { tiny_url: 'https://tinyurl.com/eyeling-test' },
995
+ assert.equal(longShare.needsGistShare, true, 'Expected oversized embedded state to request a Gist-backed sharing option');
996
+ assert.equal(longShare.hasEmbeddedState, true, 'Expected oversized edited program to be an embedded state link');
997
+ assert.match(longShare.stateUrlShare, /[?&]stateurl=/, 'Expected stateurl= links to be supported for externally stored state');
998
+ assert.doesNotMatch(longShare.stateUrlShare, /[?&]state=/, 'Expected externally stored state links to avoid embedded state payloads');
999
+ const gistShare = await createGistBackedShareUrlWithStubInPage('github-gist-token-123', {
1000
+ files: {
1001
+ 'eyeling-playground-state.json': {
1002
+ raw_url: 'https://gist.githubusercontent.com/user/id/raw/eyeling-playground-state.json',
1003
+ },
1004
+ },
980
1005
  });
981
- assert.equal(tinyUrlCreated.tinyUrl, 'https://tinyurl.com/eyeling-test', 'Expected TinyURL API response to produce a short URL');
982
- assert.equal(tinyUrlCreated.seen.url, 'https://api.tinyurl.com/create', 'Expected TinyURL API create endpoint');
983
- assert.equal(tinyUrlCreated.seen.options.method, 'POST', 'Expected TinyURL API POST request');
984
- assert.equal(tinyUrlCreated.seen.options.headers.Authorization, 'Bearer test-token-123', 'Expected bearer token authorization');
985
- assert.match(String(tinyUrlCreated.seen.options.body || ''), /"url":/, 'Expected TinyURL API body to include the long URL');
1006
+ assert.match(gistShare.shareUrl, /[?&]stateurl=/, 'Expected Gist-backed share URL to use a compact stateurl parameter');
1007
+ assert.doesNotMatch(gistShare.shareUrl, /[?&]state=/, 'Expected Gist-backed share URL not to embed the compressed state');
1008
+ assert.ok(gistShare.shareUrl.length < 300, 'Expected Gist-backed share URL to stay small');
1009
+ assert.equal(gistShare.seen.url, 'https://api.github.com/gists', 'Expected GitHub Gist create endpoint');
1010
+ assert.equal(gistShare.seen.options.method, 'POST', 'Expected GitHub Gist API POST request');
1011
+ assert.equal(gistShare.seen.options.headers.Authorization, 'Bearer github-gist-token-123', 'Expected bearer token authorization');
1012
+ assert.equal(gistShare.seen.options.referrerPolicy, 'no-referrer', 'Expected GitHub Gist API request not to send a long Referer');
1013
+ assert.match(String(gistShare.seen.options.body || ''), /"public":false/, 'Expected a secret Gist, not a public Gist');
1014
+ assert.match(String(gistShare.seen.options.body || ''), /eyeling-playground-state\.json/, 'Expected shared state to be saved as JSON');
1015
+ assert.match(String(gistShare.seen.options.body || ''), /\\"e\\":/, 'Expected compact editor state in the Gist payload');
986
1016
  endTest();
987
1017
 
988
1018
  // 7) log:query can produce Turtle; that should stay in plain source output without Markdown tabs.