Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/hide-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@matdata/yasgui": minor
"@matdata/yasqe": minor
---

Hide the query editor so the results take the whole card (#258): a "Hide query" button in the editor toolbar and a Ctrl+K command, remembered per tab. Share links can open with the query hidden (`hideQuery=true`, offered as "Show only the results" in the share popup), and the `hideQuery` config option opens new tabs that way. An update or invalid query from a link is always shown. The share popup is redesigned in the MatData look: the link with a Copy button, the option, the shortener and the cURL, PowerShell and wget commands, with inline "Copied" feedback instead of toasts. Yasqe's `createShareableLink` now receives `{ hideQuery }`, and the new `shareHideQueryOption` config shows the option.
26 changes: 17 additions & 9 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,10 @@ interface Config {
// Layout orientation: 'vertical' or 'horizontal'
orientation?: 'vertical' | 'horizontal'; // default: 'vertical'

// Open new tabs with the query editor hidden, showing only the results. Users can show it
// with "Show query"; share links can set it with hideQuery=true
hideQuery?: boolean; // default: false

// "Describe endpoint" panel (see Endpoint Describe Configuration)
endpointDescribe: EndpointDescribeConfig;

Expand Down Expand Up @@ -1101,8 +1105,13 @@ Configure how users can share their SPARQL queries with multiple output formats
interface YasqeConfig {
// ... other config options

// Function to create a shareable link (required to show share button)
createShareableLink?: (yasqe: Yasqe) => string;
// Function to create a shareable link (required to show share button).
// options.hideQuery is set when the user picked "Show only the results"
createShareableLink?: (yasqe: Yasqe, options?: { hideQuery?: boolean }) => string;

// Offer "Show only the results" in the share popup; returns the initial state of the option.
// Yasgui sets this to whether the query of the tab is hidden
shareHideQueryOption?: ((yasqe: Yasqe) => boolean) | null;

// Optional URL shortener function
createShortLink?: (yasqe: Yasqe, longUrl: string) => Promise<string>;
Expand Down Expand Up @@ -1364,11 +1373,10 @@ createShortLink: async (yasqe, longUrl) => {

When the share button is enabled (by configuring `createShareableLink`), users can:

1. **Copy URL** - Copies the shareable URL to clipboard
2. **Shorten URL** - Creates and copies a shortened URL (only shown if `createShortLink` is configured)
3. **Copy cURL** - Generates a cURL command with all headers and authentication
4. **Copy PowerShell** - Generates a PowerShell `Invoke-WebRequest` command
5. **Copy wget** - Generates a wget command
1. **Link** - Shows the shareable URL with a **Copy** button
2. **Show only the results** - Adds `hideQuery=true` to the link (only shown if `shareHideQueryOption` is configured, as Yasgui does)
3. **Shorten link** - Creates and copies a shortened URL (only shown if `createShortLink` is configured)
4. **cURL**, **PowerShell** (`Invoke-WebRequest`) and **wget** - Copy the query as a command

All command formats include:
- Complete SPARQL query
Expand All @@ -1381,7 +1389,7 @@ All command formats include:

**Security Considerations:**

⚠️ 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.
⚠️ When the request includes authentication credentials, the share popup shows a note that the command-line formats (cURL, PowerShell, wget) include them. This helps prevent accidental sharing of sensitive credentials.

**Best Practices:**

Expand All @@ -1394,7 +1402,7 @@ All command formats include:
**Troubleshooting:**

- If the share button doesn't appear, ensure `createShareableLink` is configured
- If "Shorten URL" doesn't appear, check that `createShortLink` is configured
- If "Shorten link" doesn't appear, check that `createShortLink` is configured
- Check browser console for API errors when shortening fails
- Verify API keys and endpoints are correct
- Ensure CORS is properly configured for your URL shortener API
Expand Down
35 changes: 19 additions & 16 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- [Features](#features)
- [Themes](#themes)
- [Layout Orientation](#layout-orientation)
- [Hide the Query](#hide-the-query)
- [Query Formatting](#query-formatting)
- [CONSTRUCT Query Validation](#construct-query-validation)
- [Code Snippets](#code-snippets)
Expand Down Expand Up @@ -585,6 +586,16 @@ MAtGUI offers two layout options to optimize screen space:
- Horizontal layout: drag the line between the editor and the results; double-click it to go back to a 50/50 split
- Like the side panel's edge, the handle shows a blue line when you hover or drag it

### Hide the Query

When you only care about the results, for example to look at a result someone sent you, you can fold the query editor away so the results take the whole card.

- Click **Hide query** at the right end of the editor toolbar, or search for "Hide query" with `Ctrl+K`
- The editor folds to one line that says "The query is hidden". **Share**, the save buttons and **Run** stay available
- Click **Show query** (or the "The query is hidden" line) to bring the editor back
- Each tab remembers whether its query is hidden
- To share a link that opens with the query hidden, tick **Show only the results** in the share popup (see [Share Queries](#share-queries))

### Query Formatting

Keep your SPARQL queries clean and readable with automatic formatting.
Expand Down Expand Up @@ -1677,28 +1688,20 @@ Share your queries with colleagues using multiple formats including URL paramete

**Share Button:**

YASQE provides a share button (when `createShareableLink` is configured) that offers multiple sharing options:

1. **Copy URL** - Copies the shareable URL to your clipboard
2. **Shorten URL** - Creates a shortened URL (if URL shortener is configured)
3. **Copy cURL** - Generates and copies a cURL command for command-line execution
4. **Copy PowerShell** - Generates and copies a PowerShell command using `Invoke-WebRequest`
5. **Copy wget** - Generates and copies a wget command

**How to Share:**
The share button in the editor toolbar opens the share popup, with two parts:

Matgui supports URL-based query sharing:
- **Link**: the link to the query, with a **Copy** button
- **Show only the results**: the link opens with the query hidden, so the reader sees the results straight away (see [Hide the Query](#hide-the-query)). The option starts ticked when the query of your tab is hidden
- **Shorten link**: creates and copies a short link (only when a URL shortener is configured)
- **Command line**: click **cURL**, **PowerShell** (`Invoke-WebRequest`) or **wget** to copy the query as a command

1. Craft your query in MatGUI
2. Click the share button (sharing icon in the top-right corner)
3. Select your preferred format
4. The content is automatically copied to your clipboard
5. Share with others or execute in your terminal
The button you clicked shows **Copied** for a moment. Press `Esc` or click outside the popup to close it.

**URL Parameters:**

- `query`: The SPARQL query
- `endpoint`: The endpoint URL
- `hideQuery=true`: open the link with the query editor hidden
- Other tab configurations

**Opening a shared link:** the query of the link opens in a tab and runs straight away, except in these cases, where MatGUI shows a message and waits for you:
Expand All @@ -1716,7 +1719,7 @@ All command-line formats (cURL, PowerShell, wget) include:
- Authentication credentials (if configured)
- Proper HTTP method (GET or POST)

⚠️ **Security Notice:** When copying commands with authentication credentials, a warning notification will appear. Be cautious about sharing these commands as they contain sensitive authentication information.
⚠️ **Security Notice:** When the endpoint has authentication credentials, the share popup says that the commands include them. Be cautious about sharing these commands as they contain sensitive authentication information.

**Example Formats:**

Expand Down
7 changes: 7 additions & 0 deletions packages/yasgui/src/CommandPalette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ export default class CommandPalette {
keywords: "orientation stacked horizontal vertical",
run: () => tab()?.toggleOrientation(),
},
{
section: "commands",
label: tab()?.isQueryHidden() ? "Show query" : "Hide query",
icon: "code",
keywords: "editor collapse expand results only toggle",
run: () => tab()?.toggleQueryHidden(),
},
);
const tools: Array<[string, string, IconName, string]> = [
[".yasqe_formatButton", "Format query", "alignLeft", "beautify indent"],
Expand Down
83 changes: 78 additions & 5 deletions packages/yasgui/src/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface PersistedJson {
requestConfig: YasguiRequestConfig;
orientation?: "vertical" | "horizontal";
managedQuery?: ManagedTabMetadata;
/** The query editor is hidden, so the results take the whole card (e.g. a link to a result) */
queryHidden?: boolean;
}

export interface Tab {
Expand Down Expand Up @@ -94,6 +96,7 @@ export class Tab extends EventEmitter {
private titleEl?: HTMLDivElement;
private currentOrientation: "vertical" | "horizontal";
private orientationToggleButton?: HTMLButtonElement;
private queryToggleButton?: HTMLButtonElement;
private editorToolsEl?: HTMLDivElement;
private verticalResizerEl?: HTMLDivElement;
private editorWrapperEl?: HTMLDivElement;
Expand Down Expand Up @@ -401,6 +404,7 @@ export class Tab extends EventEmitter {

// Apply orientation class
addClass(this.rootEl, `orientation-${this.currentOrientation}`);
if (this.isQueryHidden()) addClass(this.rootEl, "queryHidden");

// We group controlbar and Yasqe, so that users can easily .appendChild() to the .editorwrapper div
// to add a div that goes alongside the controlbar and editor, while YASR still goes full width
Expand Down Expand Up @@ -759,7 +763,73 @@ export class Tab extends EventEmitter {
}

this.initOrientationToggle();
this.initQueryToggle();
this.yasqeWrapperEl.appendChild(toolbar);

// Shown instead of the editor while the query is hidden
const note = document.createElement("button");
note.type = "button";
addClass(note, "yasgui-queryHiddenNote");
note.innerHTML = `${icon("code", 14)}<span>The query is hidden</span>`;
note.title = "Show the query";
note.addEventListener("click", () => this.setQueryHidden(false));
this.yasqeWrapperEl.appendChild(note);
}

/** Button at the end of the editor toolbar that hides or shows the query editor */
private initQueryToggle() {
if (!this.editorToolbarEl) return;
const button = document.createElement("button");
button.type = "button";
button.className = "yasgui-editorToolbar__button yasgui-queryToggle";
button.addEventListener("click", () => this.toggleQueryHidden());
this.queryToggleButton = button;
this.updateQueryToggle();
this.editorToolbarEl.appendChild(button);
}

private updateQueryToggle() {
const button = this.queryToggleButton;
if (!button) return;
const hidden = this.isQueryHidden();
const label = hidden ? "Show query" : "Hide query";
button.innerHTML = `${icon(hidden ? "chevronDown" : "chevronUp")}<span class="yasgui-btnLabel">${label}</span>`;
button.title = hidden ? "Show the query editor" : "Hide the query editor and show only the results";
button.setAttribute("aria-label", label);
button.setAttribute("aria-expanded", hidden ? "false" : "true");
}

/** Whether the query editor of this tab is hidden, showing only the results */
public isQueryHidden(): boolean {
return !!this.persistentJson.queryHidden;
}

/** Hide the query editor so the results take the whole card, or show it again */
public setQueryHidden(hidden: boolean) {
if (this.isQueryHidden() === hidden) return;
if (hidden) {
this.persistentJson.queryHidden = true;
if (this.yasqe?.getIsFullscreen()) this.yasqe.toggleFullscreen();
} else {
delete this.persistentJson.queryHidden;
}
if (this.rootEl) {
if (hidden) addClass(this.rootEl, "queryHidden");
else removeClass(this.rootEl, "queryHidden");
}
this.updateQueryToggle();
if (!hidden) {
this.yasqe?.refresh();
this.yasqe?.focus();
} else {
this.queryToggleButton?.focus();
}
this.yasr?.refresh();
this.emit("change", this, this.persistentJson);
}

public toggleQueryHidden() {
this.setQueryHidden(!this.isQueryHidden());
}

/** The panel of this tab, holding the query card */
Expand Down Expand Up @@ -1107,12 +1177,13 @@ export class Tab extends EventEmitter {
this.getTabListEl().redrawContextMenu();
}

public getShareableLink(baseURL?: string): string {
return shareLink.createShareLink(baseURL || window.location.href, this);
/** Link that opens this query; `hideQuery` opens it with the query editor hidden */
public getShareableLink(baseURL?: string, options?: shareLink.ShareLinkOptions): string {
return shareLink.createShareLink(baseURL || window.location.href, this, options);
}

public getShareObject() {
return shareLink.createShareConfig(this);
public getShareObject(options?: shareLink.ShareLinkOptions) {
return shareLink.createShareConfig(this, options);
}

private getTabListEl(): TabListEl {
Expand Down Expand Up @@ -1423,7 +1494,8 @@ export class Tab extends EventEmitter {
editorHeight: this.persistentJson.yasqe.editorHeight ? this.persistentJson.yasqe.editorHeight : undefined,
persistenceId: null, //yasgui handles persistent storing
consumeShareLink: null, //not handled by this tab, but by parent Matgui instance
createShareableLink: () => this.getShareableLink(),
createShareableLink: (_yasqe, options) => this.getShareableLink(undefined, options),
shareHideQueryOption: () => this.isQueryHidden(),
// The snippets bar is off unless enabled (config or settings); the snippets are in the Insert menu
showSnippetsBar: this.yasgui.config.showSnippetsBar === true,
// Yasgui shows its own Run button in the query bar
Expand Down Expand Up @@ -2074,6 +2146,7 @@ export class Tab extends EventEmitter {
requestConfig: yasgui ? yasgui.config.requestConfig : { ...Yasgui.defaults.requestConfig },
id: getRandomId(),
name: yasgui ? yasgui.createTabName() : Yasgui.defaults.tabName,
...(yasgui?.config.hideQuery ? { queryHidden: true } : {}),
};
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/yasgui/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default function initialize(): Config<CatalogueItem> {
showQuickStart: true,
quickStartOnFirstVisit: true,
orientation: "vertical",
hideQuery: false,
showSnippetsBar: false,
iriHover: true,
iriHoverLovFallback: true,
Expand Down
10 changes: 10 additions & 0 deletions packages/yasgui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ export interface Config<EndpointObject extends CatalogueItem = CatalogueItem> {
* 'horizontal': YASQE on left, YASR on right
*/
orientation?: "vertical" | "horizontal";
/**
* Open new tabs with the query editor hidden, so only the results show (default: false). Users can
* show the query with the "Show query" button. Share links can also set this with `hideQuery=true`.
*/
hideQuery?: boolean;
/**
* Show the code snippets bar above the editor in all tabs (global setting).
* Off by default: the snippets are available from the Insert menu of the query bar.
Expand Down Expand Up @@ -335,6 +340,9 @@ export class Yasgui extends EventEmitter {
if (tabId) {
// when a config is already present,
const persistentYasr = this.persistentConfig.getTab(tabId).yasr;
const existingTab = this.persistentConfig.getTab(tabId);
if (optionsFromUrl.queryHidden) existingTab.queryHidden = true;
else delete existingTab.queryHidden;
this.persistentConfig.getTab(tabId).yasr = {
// Override the settings
settings: optionsFromUrl.yasr.settings,
Expand Down Expand Up @@ -481,6 +489,8 @@ export class Yasgui extends EventEmitter {
tab.query().catch(() => {});
return;
}
// An update or invalid query is not run, so the reader needs to see it
if ((blocker === "update" || blocker === "invalid") && tab.isQueryHidden()) tab.setQueryHidden(false);
const canRun = blocker === "credentials" || blocker === "disabled";
showToast(shareLink.sharedQueryBlockedMessage(blocker, tab.getEndpoint()), {
host: this.rootEl,
Expand Down
15 changes: 12 additions & 3 deletions packages/yasgui/src/linkUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import JsUri from "jsuri";
import { default as Tab, PersistedJson } from "./Tab";
import Yasr from "@matdata/yasr";
import { PlainRequestConfig } from "@matdata/yasqe";
import type { ShareLinkOptions } from "@matdata/yasqe";
import { getAsValue } from "@matdata/yasgui-utils";
import { parseJsonParam } from "./shareLinkSafety";
export * from "./shareLinkSafety";
/** Options of a share link, picked in the share popup */
export type { ShareLinkOptions } from "@matdata/yasqe";
var getUrlParams = function (_url?: string) {
var urlFromWindow = false;
if (!_url) {
Expand Down Expand Up @@ -37,10 +40,10 @@ export function appendArgsToUrl(_url: string, args: RequestArgs): string {
}
return url.toString();
}
export function createShareLink(forUrl: string, tab: Tab) {
export function createShareLink(forUrl: string, tab: Tab, options?: ShareLinkOptions) {
const currentUrl = new JsUri(forUrl);
const tmpUrl = new JsUri();
const configObject = createShareConfig(tab);
const configObject = createShareConfig(tab, options);
let key: keyof ShareConfigObject; // Need to specify here because left hand side of a for..in cannot be typed
for (key in configObject) {
if (!configObject.hasOwnProperty(key)) continue;
Expand Down Expand Up @@ -85,9 +88,11 @@ export type ShareConfigObject = {
defaultGraphs: PlainRequestConfig["defaultGraphs"];
outputFormat?: string;
outputSettings: any;
/** Open the link with the query editor hidden */
hideQuery?: boolean;
};

export function createShareConfig(tab: Tab): ShareConfigObject {
export function createShareConfig(tab: Tab, options?: ShareLinkOptions): ShareConfigObject {
const yasgui = tab.yasgui;
const requestConfig = tab.getRequestConfig();
const yasrPersistentSetting = tab.getPersistedJson().yasr.settings;
Expand All @@ -108,6 +113,7 @@ export function createShareConfig(tab: Tab): ShareConfigObject {
yasrPersistentSetting.pluginsConfig && yasrPersistentSetting.selectedPlugin
? yasrPersistentSetting.pluginsConfig[yasrPersistentSetting.selectedPlugin]
: undefined,
...(options?.hideQuery ? { hideQuery: true } : {}),
};
}

Expand Down Expand Up @@ -140,6 +146,9 @@ export function getConfigFromUrl(defaults: PersistedJson, _url?: string): Persis
options.requestConfig.method = <any>value;
} else if (key == "tabTitle") {
options.name = value;
} else if (key == "hideQuery") {
if (value === "false" || value === "0") delete options.queryHidden;
else options.queryHidden = true;
} else if (key == "namedGraph") {
if (!Array.isArray(options.requestConfig.namedGraphs)) options.requestConfig.namedGraphs = [];
options.requestConfig.namedGraphs.push(value);
Expand Down
Loading
Loading