Skip to content

Commit e09e044

Browse files
CopilotMathiasVDACopilot
authored
feat: add PowerShell and wget share formats with clipboard copy and credential warnings + add URL shortening service (#89)
* Initial plan * Add improved share functionality with PowerShell, wget, and clipboard support Co-authored-by: MathiasVDA <[email protected]> * Add comprehensive documentation for share functionality and URL shortener Co-authored-by: MathiasVDA <[email protected]> * Address code review feedback - improve auth detection and add fallback for non-standard HTTP methods Co-authored-by: MathiasVDA <[email protected]> * Final code quality improvements - extract constants and fix edge cases Co-authored-by: MathiasVDA <[email protected]> * fix wget command * add all accept headers to the shareable code * better layout for the warning toast * add yourls example for the url shortener service * Update packages/yasqe/src/sparql.ts Co-authored-by: Copilot <[email protected]> * Address PR review feedback - fix escaping, memory leaks, and add clipboard fallback Co-authored-by: MathiasVDA <[email protected]> * chore: added shortening service to dev page --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: MathiasVDA <[email protected]> Co-authored-by: Mathias Vanden Auweele <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 8d0a6a7 commit e09e044

File tree

7 files changed

+1194
-93
lines changed

7 files changed

+1194
-93
lines changed

dev/yasgui.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,37 @@
3636
{ endpoint: "https://data-interop.era.europa.eu/api/sparql", label: "ERA" }
3737
],
3838
yasqe: {
39+
createShortLink: async (yasqe, longUrl) => {
40+
const YOURLS_API_URL = 'https://shorter.matdata.eu/api.php';
41+
42+
try {
43+
const params = new URLSearchParams({
44+
action: 'shorturl',
45+
url: longUrl,
46+
format: 'json',
47+
});
48+
49+
const response = await fetch(`${YOURLS_API_URL}?${params.toString()}`, {
50+
method: 'GET'
51+
});
52+
53+
if (!response.ok) {
54+
throw new Error(`HTTP error! status: ${response.status}`);
55+
}
56+
57+
const data = await response.json();
58+
59+
if (data.status === 'fail') {
60+
throw new Error(data.message || 'Failed to shorten URL');
61+
}
62+
63+
return data.shorturl;
64+
65+
} catch (error) {
66+
console.error('YOURLS shortening error:', error);
67+
throw error;
68+
}
69+
},
3970
snippets: [
4071
{ label: "SELECT", code: "SELECT * WHERE {\n ?s ?p ?o .\n} LIMIT 10", group: "QUERY" },
4172
{ label: "CONSTRUCT", code: "CONSTRUCT {\n ?s ?p ?o .\n} WHERE {\n ?s ?p ?o .\n} LIMIT 10", group: "QUERY" },

docs/developer-guide.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ This comprehensive guide covers everything developers need to know to integrate,
3333
- [YASQE Configuration](#yasqe-configuration)
3434
- [YASQE Example](#yasqe-example)
3535
- [Code Snippets](#code-snippets)
36+
- [Share Configuration](#share-configuration)
3637
- [YASR Configuration](#yasr-configuration)
3738
- [YASR Example](#yasr-example)
3839
- [Request Configuration](#request-configuration)
@@ -920,6 +921,314 @@ const yasqe = new Yasqe(document.getElementById('yasqe'), {
920921
});
921922
```
922923

924+
#### Share Configuration
925+
926+
Configure how users can share their SPARQL queries with multiple output formats including URLs, cURL, PowerShell, and wget commands.
927+
928+
**Configuration Options:**
929+
930+
```typescript
931+
interface YasqeConfig {
932+
// ... other config options
933+
934+
// Function to create a shareable link (required to show share button)
935+
createShareableLink?: (yasqe: Yasqe) => string;
936+
937+
// Optional URL shortener function
938+
createShortLink?: (yasqe: Yasqe, longUrl: string) => Promise<string>;
939+
940+
// Function to consume/parse shared links
941+
consumeShareLink?: (yasqe: Yasqe) => void;
942+
}
943+
```
944+
945+
**Basic Share Configuration:**
946+
947+
```javascript
948+
import Yasqe from '@matdata/yasqe';
949+
950+
const yasqe = new Yasqe(document.getElementById('yasqe'), {
951+
requestConfig: {
952+
endpoint: 'https://dbpedia.org/sparql',
953+
method: 'POST'
954+
},
955+
956+
// Enable share button with URL generation
957+
createShareableLink: (yasqe) => {
958+
const query = yasqe.getValue();
959+
const endpoint = yasqe.getRequestConfig().endpoint;
960+
961+
// Create shareable URL with query parameters
962+
const params = new URLSearchParams({
963+
query: query,
964+
endpoint: endpoint
965+
});
966+
967+
return `${window.location.origin}${window.location.pathname}?${params.toString()}`;
968+
},
969+
970+
// Parse shared URLs on page load
971+
consumeShareLink: (yasqe) => {
972+
const params = new URLSearchParams(window.location.search);
973+
const query = params.get('query');
974+
const endpoint = params.get('endpoint');
975+
976+
if (query) yasqe.setValue(query);
977+
if (endpoint) {
978+
yasqe.setRequestConfig({
979+
...yasqe.getRequestConfig(),
980+
endpoint: endpoint
981+
});
982+
}
983+
}
984+
});
985+
```
986+
987+
**URL Shortener Configuration (Kutt Example):**
988+
989+
Configure a URL shortener service to create shorter, more shareable links. This example shows integration with [Kutt](https://kutt.it), but any URL shortener API can be used.
990+
991+
```javascript
992+
import Yasqe from '@matdata/yasqe';
993+
994+
const yasqe = new Yasqe(document.getElementById('yasqe'), {
995+
requestConfig: {
996+
endpoint: 'https://dbpedia.org/sparql'
997+
},
998+
999+
// Basic shareable link creation
1000+
createShareableLink: (yasqe) => {
1001+
const query = yasqe.getValue();
1002+
const config = yasqe.getRequestConfig();
1003+
1004+
const params = new URLSearchParams({
1005+
query: query,
1006+
endpoint: config.endpoint || ''
1007+
});
1008+
1009+
return `${window.location.origin}${window.location.pathname}?${params.toString()}`;
1010+
},
1011+
1012+
// URL shortener integration (Kutt example)
1013+
createShortLink: async (yasqe, longUrl) => {
1014+
const KUTT_API_URL = 'https://kutt.it/api/v2/links';
1015+
// Important: Load API key from a secure config or environment variable
1016+
// Never hardcode real API keys in source code!
1017+
// Example: Load from window.__CONFIG__ set by server-rendered template
1018+
const KUTT_API_KEY = window.__CONFIG__?.KUTT_API_KEY;
1019+
1020+
if (!KUTT_API_KEY) {
1021+
throw new Error('Kutt API key not configured. Load it from a secure config or environment variable.');
1022+
}
1023+
1024+
try {
1025+
const response = await fetch(KUTT_API_URL, {
1026+
method: 'POST',
1027+
headers: {
1028+
'Content-Type': 'application/json',
1029+
'X-API-Key': KUTT_API_KEY
1030+
},
1031+
body: JSON.stringify({
1032+
target: longUrl,
1033+
// Optional: custom short code
1034+
// customurl: 'my-custom-code',
1035+
// Optional: domain (if using custom domain)
1036+
// domain: 'example.com',
1037+
// Optional: expiration
1038+
// expire_in: '2 hours'
1039+
})
1040+
});
1041+
1042+
if (!response.ok) {
1043+
const error = await response.json();
1044+
throw new Error(error.message || 'Failed to shorten URL');
1045+
}
1046+
1047+
const data = await response.json();
1048+
return data.link; // Returns shortened URL
1049+
1050+
} catch (error) {
1051+
console.error('URL shortening error:', error);
1052+
throw error; // Error will be displayed to user
1053+
}
1054+
},
1055+
1056+
consumeShareLink: (yasqe) => {
1057+
const params = new URLSearchParams(window.location.search);
1058+
const query = params.get('query');
1059+
if (query) yasqe.setValue(query);
1060+
}
1061+
});
1062+
```
1063+
1064+
**Other URL Shortener Examples:**
1065+
1066+
*YOURLS (Your Own URL Shortener):*
1067+
```javascript
1068+
createShortLink: async (yasqe, longUrl) => {
1069+
const YOURLS_API_URL = 'https://your-domain.com/yourls-api.php'; // Your YOURLS instance
1070+
const YOURLS_SIGNATURE = 'your-signature-token'; // Found in YOURLS admin > Tools
1071+
1072+
try {
1073+
const params = new URLSearchParams({
1074+
signature: YOURLS_SIGNATURE,
1075+
action: 'shorturl',
1076+
url: longUrl,
1077+
format: 'json',
1078+
// Optional: custom keyword
1079+
// keyword: 'my-custom-keyword'
1080+
});
1081+
1082+
const response = await fetch(`${YOURLS_API_URL}?${params.toString()}`, {
1083+
method: 'GET'
1084+
});
1085+
1086+
if (!response.ok) {
1087+
throw new Error(`HTTP error! status: ${response.status}`);
1088+
}
1089+
1090+
const data = await response.json();
1091+
1092+
if (data.status === 'fail') {
1093+
throw new Error(data.message || 'Failed to shorten URL');
1094+
}
1095+
1096+
return data.shorturl; // Returns shortened URL
1097+
1098+
} catch (error) {
1099+
console.error('YOURLS shortening error:', error);
1100+
throw error;
1101+
}
1102+
}
1103+
```
1104+
1105+
**Note:** YOURLS can authenticate using either a signature token (recommended) or username/password. The signature token is more secure and can be found in your YOURLS admin panel under Tools > Signature Token.
1106+
1107+
*TinyURL:*
1108+
```javascript
1109+
createShortLink: async (yasqe, longUrl) => {
1110+
try {
1111+
const response = await fetch(
1112+
`https://tinyurl.com/api-create.php?url=${encodeURIComponent(longUrl)}`
1113+
);
1114+
1115+
if (!response.ok) {
1116+
throw new Error(`TinyURL request failed with status ${response.status}`);
1117+
}
1118+
1119+
const shortUrl = await response.text();
1120+
1121+
// Basic validation: ensure the response is a valid TinyURL link
1122+
let parsedUrl;
1123+
try {
1124+
parsedUrl = new URL(shortUrl);
1125+
} catch {
1126+
throw new Error('TinyURL response was not a valid URL');
1127+
}
1128+
1129+
if (!/^https?:\/\/(www\.)?tinyurl\.com\//.test(parsedUrl.href)) {
1130+
throw new Error('TinyURL response did not contain a TinyURL link');
1131+
}
1132+
1133+
return parsedUrl.href;
1134+
} catch (error) {
1135+
console.error('TinyURL shortening error:', error);
1136+
throw error;
1137+
}
1138+
}
1139+
```
1140+
1141+
*Bitly:*
1142+
```javascript
1143+
createShortLink: async (yasqe, longUrl) => {
1144+
// Load token from secure config
1145+
const BITLY_TOKEN = window.__CONFIG__?.BITLY_TOKEN;
1146+
1147+
if (!BITLY_TOKEN) {
1148+
throw new Error('Bitly token not configured');
1149+
}
1150+
1151+
try {
1152+
const response = await fetch('https://api-ssl.bitly.com/v4/shorten', {
1153+
method: 'POST',
1154+
headers: {
1155+
'Authorization': `Bearer ${BITLY_TOKEN}`,
1156+
'Content-Type': 'application/json'
1157+
},
1158+
body: JSON.stringify({ long_url: longUrl })
1159+
});
1160+
1161+
if (!response.ok) {
1162+
throw new Error(`Bitly API request failed with status ${response.status}`);
1163+
}
1164+
1165+
const data = await response.json();
1166+
1167+
if (!data || typeof data.link !== 'string') {
1168+
throw new Error('Bitly API response missing expected "link" property');
1169+
}
1170+
1171+
return data.link;
1172+
} catch (error) {
1173+
console.error('Failed to create Bitly short link:', error);
1174+
throw error;
1175+
}
1176+
}
1177+
```
1178+
1179+
*Custom Backend:*
1180+
```javascript
1181+
createShortLink: async (yasqe, longUrl) => {
1182+
const response = await fetch('/api/shorten', {
1183+
method: 'POST',
1184+
headers: { 'Content-Type': 'application/json' },
1185+
body: JSON.stringify({ url: longUrl })
1186+
});
1187+
1188+
const data = await response.json();
1189+
return data.shortUrl;
1190+
}
1191+
```
1192+
1193+
**Share Button Features:**
1194+
1195+
When the share button is enabled (by configuring `createShareableLink`), users can:
1196+
1197+
1. **Copy URL** - Copies the shareable URL to clipboard
1198+
2. **Shorten URL** - Creates and copies a shortened URL (only shown if `createShortLink` is configured)
1199+
3. **Copy cURL** - Generates a cURL command with all headers and authentication
1200+
4. **Copy PowerShell** - Generates a PowerShell `Invoke-WebRequest` command
1201+
5. **Copy wget** - Generates a wget command
1202+
1203+
All command formats include:
1204+
- Complete SPARQL query
1205+
- Endpoint URL
1206+
- HTTP method (GET/POST)
1207+
- All configured headers
1208+
- Accept header (based on query type: JSON for SELECT/ASK, text/turtle for CONSTRUCT/DESCRIBE)
1209+
- Authentication credentials (with security warning)
1210+
- Output file specification (PowerShell only, with appropriate extension based on Accept header)
1211+
1212+
**Security Considerations:**
1213+
1214+
⚠️ When users copy command-line formats (cURL, PowerShell, wget) that include authentication credentials, YASQE displays a warning toast notification. This helps prevent accidental sharing of sensitive credentials.
1215+
1216+
**Best Practices:**
1217+
1218+
1. **Store API keys securely** - Never commit API keys to version control
1219+
2. **Use environment variables** - Load API keys from environment or configuration
1220+
3. **Implement rate limiting** - Prevent abuse of URL shortener services
1221+
4. **Handle errors gracefully** - Provide user-friendly error messages
1222+
5. **Consider privacy** - Be transparent about what data is included in shared URLs
1223+
1224+
**Troubleshooting:**
1225+
1226+
- If the share button doesn't appear, ensure `createShareableLink` is configured
1227+
- If "Shorten URL" doesn't appear, check that `createShortLink` is configured
1228+
- Check browser console for API errors when shortening fails
1229+
- Verify API keys and endpoints are correct
1230+
- Ensure CORS is properly configured for your URL shortener API
1231+
9231232
### YASR Configuration
9241233
9251234
YASR (results viewer) specific configuration options:

0 commit comments

Comments
 (0)