diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 989dd75b6a..1b704a3e67 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 8.0 +**Feature level 196** + +* [`POST /realm/playgrounds`](/api/add-code-playground): `url_prefix` is + replaced by `url_template`, which only accepts [RFC 6570][rfc6570] compliant + URL templates. The old prefix format is no longer supported. +* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue): + `url_prefix` is replaced by `url_template` in `realm_playgrounds` events. + **Feature level 195** * [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue): diff --git a/help/code-blocks.md b/help/code-blocks.md index e22d58037d..c8c00467f9 100644 --- a/help/code-blocks.md +++ b/help/code-blocks.md @@ -67,15 +67,24 @@ prefix**. {end_tabs} For example, to configure code playgrounds for languages like Python or -JavaScript, you could specify the language and URL prefix fields as: +JavaScript, you could specify the language and URL templates as: -* `Python` and `https://replit.com/languages/python3/?code=` -* `JavaScript` and `https://replit.com/languages/javascript/?code=` +* `Python` and `https://replit.com/languages/python3/code={code}` +* `JavaScript` and `https://replit.com/languages/javascript/code={code}` When a code block is labeled as Python or JavaScript (either explicitly or by organization default), users would get a on-hover option to open the code block in the specified code playground. +!!! tip "" + + Code playgrounds use [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.html) + compliant URL templates to describe how links should be generated. Zulip's + rendering engine will pass the URL-encoded code from the code block as the + `code` parameter, denoted as `{code}` in this URL template, in order to + generate the URL. You can refer to parts of the documentation on URL + templates from [adding a custom linkifier](/help/add-a-custom-linkifier). + ### Technical details * You can configure multiple playgrounds for a given language; if you do that, @@ -87,8 +96,9 @@ to these human-readable Pygments names; e.g., `py3` and `py` are mapped to `Python`. One can use the typeahead (which appears when you type something or just click on the language field) to look up the Pygments name. -* The links for opening code playgrounds are always constructed by concatenating -the provided URL prefix with the URL-encoded contents of the code block. +* The links for opening code playgrounds are always constructed by substituting +the URL-encoded contents of the code block into `code` variable in the URL template. +The URL template is required to contain exactly one variable named `code`. * Code playground sites do not always clearly document their URL format; often you can just get the prefix from your browser's URL bar. diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py index eb12e351b2..5b798d6352 100644 --- a/tools/linter_lib/custom_check.py +++ b/tools/linter_lib/custom_check.py @@ -539,7 +539,7 @@ html_rules: List["Rule"] = [ }, "exclude": { "templates/analytics/support.html", - # We have URL prefix and Pygments language name as placeholders + # We have URL template and Pygments language name as placeholders # in the below template which we don't want to be translatable. "web/templates/settings/playground_settings_admin.hbs", }, @@ -553,6 +553,8 @@ html_rules: List["Rule"] = [ "description": "Likely missing quoting in HTML attribute", "good_lines": [''], "bad_lines": [""], + # Exclude the use of URL templates from this check. + "exclude_pattern": "={code}", }, { "pattern": " '}}", diff --git a/version.py b/version.py index 9ab73d9b87..974b949e2a 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # Changes should be accompanied by documentation explaining what the # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 195 +API_FEATURE_LEVEL = 196 # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/web/e2e-tests/realm-playground.test.ts b/web/e2e-tests/realm-playground.test.ts index fbec9d30a5..4818f3497a 100644 --- a/web/e2e-tests/realm-playground.test.ts +++ b/web/e2e-tests/realm-playground.test.ts @@ -7,7 +7,7 @@ import * as common from "./lib/common"; type Playground = { playground_name: string; pygments_language: string; - url_prefix: string; + url_template: string; }; async function _add_playground_and_return_status(page: Page, payload: Playground): Promise { @@ -35,7 +35,7 @@ async function test_successful_playground_creation(page: Page): Promise { const payload = { pygments_language: "Python", playground_name: "Python3 playground", - url_prefix: "https://python.example.com", + url_template: "https://python.example.com?code={code}", }; const status = await _add_playground_and_return_status(page, payload); assert.strictEqual(status, "Custom playground added!"); @@ -52,8 +52,8 @@ async function test_successful_playground_creation(page: Page): Promise { "Python3 playground", ); assert.strictEqual( - await common.get_text_from_selector(page, ".playground_row span.playground_url_prefix"), - "https://python.example.com", + await common.get_text_from_selector(page, ".playground_row span.playground_url_template"), + "https://python.example.com?code={code}", ); } @@ -61,12 +61,12 @@ async function test_invalid_playground_parameters(page: Page): Promise { const payload = { pygments_language: "Python", playground_name: "Python3 playground", - url_prefix: "not_a_url", + url_template: "not_a_url_template{", }; let status = await _add_playground_and_return_status(page, payload); - assert.strictEqual(status, "Failed: url_prefix is not a URL"); + assert.strictEqual(status, "Failed: Invalid URL template."); - payload.url_prefix = "https://python.example.com"; + payload.url_template = "https://python.example.com?code={code}"; payload.pygments_language = "py!@%&"; status = await _add_playground_and_return_status(page, payload); assert.strictEqual(status, "Failed: Invalid characters in pygments language"); diff --git a/web/src/popovers.js b/web/src/popovers.js index 7d0926a55a..b13fdf60aa 100644 --- a/web/src/popovers.js +++ b/web/src/popovers.js @@ -2,6 +2,7 @@ import ClipboardJS from "clipboard"; import {parseISO} from "date-fns"; import $ from "jquery"; import tippy, {hideAll} from "tippy.js"; +import url_template_lib from "url-template"; import render_no_arrow_popover from "../templates/no_arrow_popover.hbs"; import render_playground_links_popover_content from "../templates/playground_links_popover_content.hbs"; @@ -783,20 +784,21 @@ export function register_click_handlers() { const playground_info = realm_playground.get_playground_info_for_languages( $codehilite_div.data("code-language"), ); - // We do the code extraction here and set the target href combining the url_prefix - // and the extracted code. Depending on whether the language has multiple playground - // links configured, a popover is show. + // We do the code extraction here and set the target href expanding + // the url_template with the extracted code. Depending on whether + // the language has multiple playground links configured, a popover + // is shown. const extracted_code = $codehilite_div.find("code").text(); if (playground_info.length === 1) { - const url_prefix = playground_info[0].url_prefix; + const url_template = url_template_lib.parse(playground_info[0].url_template); $view_in_playground_button.attr( "href", - url_prefix + encodeURIComponent(extracted_code), + url_template.expand({code: extracted_code}), ); } else { for (const $playground of playground_info) { - $playground.playground_url = - $playground.url_prefix + encodeURIComponent(extracted_code); + const url_template = url_template_lib.parse($playground.url_template); + $playground.playground_url = url_template.expand({code: extracted_code}); } toggle_playground_link_popover(this, playground_info); } diff --git a/web/src/realm_playground.ts b/web/src/realm_playground.ts index d6ace3bb3c..39cddbbcd1 100644 --- a/web/src/realm_playground.ts +++ b/web/src/realm_playground.ts @@ -23,7 +23,7 @@ export function update_playgrounds(playgrounds_data: RealmPlayground[]): void { const element_to_push: Omit = { id: data.id, name: data.name, - url_prefix: data.url_prefix, + url_template: data.url_template, }; if (map_language_to_playground_info.has(data.pygments_language)) { map_language_to_playground_info.get(data.pygments_language)!.push(element_to_push); diff --git a/web/src/settings_playgrounds.js b/web/src/settings_playgrounds.js index 10f9847d36..d3687960ab 100644 --- a/web/src/settings_playgrounds.js +++ b/web/src/settings_playgrounds.js @@ -41,7 +41,7 @@ export function populate_playgrounds(playgrounds_data) { playground: { playground_name: playground.name, pygments_language: playground.pygments_language, - url_prefix: playground.url_prefix, + url_template: playground.url_template, id: playground.id, }, can_modify: page_params.is_admin, @@ -65,7 +65,7 @@ export function populate_playgrounds(playgrounds_data) { ...ListWidget.generic_sort_functions("alphabetic", [ "pygments_language", "name", - "url_prefix", + "url_template", ]), }, $simplebar_container: $("#playground-settings .progressive-table-wrapper"), @@ -110,7 +110,7 @@ function build_page() { const data = { name: $("#playground_name").val(), pygments_language: $("#playground_pygments_language").val(), - url_prefix: $("#playground_url_prefix").val(), + url_template: $("#playground_url_template").val(), }; channel.post({ url: "/json/realm/playgrounds", @@ -118,7 +118,7 @@ function build_page() { success() { $("#playground_pygments_language").val(""); $("#playground_name").val(""); - $("#playground_url_prefix").val(""); + $("#playground_url_template").val(""); $add_playground_button.prop("disabled", false); ui_report.success( $t_html({defaultMessage: "Custom playground added!"}), diff --git a/web/styles/settings.css b/web/styles/settings.css index e1a79e6ce4..2f4b034b7c 100644 --- a/web/styles/settings.css +++ b/web/styles/settings.css @@ -250,7 +250,7 @@ h3, #playground-settings { #playground_pygments_language, #playground_name, - #playground_url_prefix { + #playground_url_template { width: calc(100% - 10em - 6em); } } diff --git a/web/templates/settings/admin_playground_list.hbs b/web/templates/settings/admin_playground_list.hbs index 72f34a8173..2e785141ef 100644 --- a/web/templates/settings/admin_playground_list.hbs +++ b/web/templates/settings/admin_playground_list.hbs @@ -7,7 +7,7 @@ {{playground_name}} - {{url_prefix}} + {{url_template}} {{#if ../can_modify}} diff --git a/web/templates/settings/playground_settings_admin.hbs b/web/templates/settings/playground_settings_admin.hbs index 838d64e735..a303cfac72 100644 --- a/web/templates/settings/playground_settings_admin.hbs +++ b/web/templates/settings/playground_settings_admin.hbs @@ -23,7 +23,7 @@ {{t "Name" }}: Python3 playground
  • - {{t "URL prefix" }}: https://replit.com/languages/python3/?code= + {{t "URL template" }}: https://replit.com/languages/python3/?code={code}
  • @@ -52,8 +52,8 @@

    - - + +