mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-18 11:01:36 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d4917d328 | ||
|
|
edea311757 | ||
|
|
e7eacc32c0 | ||
|
|
12f50cca8d | ||
|
|
88a4dba695 | ||
|
|
634ea0ab6a | ||
|
|
cd0b5cbc7a | ||
|
|
0b7de6608a | ||
|
|
8c6bd8c857 | ||
|
|
def5775e00 | ||
|
|
91da9aea98 | ||
|
|
1c898402f8 | ||
|
|
54210cf479 | ||
|
|
cbcea87a82 | ||
|
|
2d50d2c5d1 | ||
|
|
7448c6c32c | ||
|
|
3196cf5be0 | ||
|
|
666726a5fa | ||
|
|
818a3d204e | ||
|
|
d45758b4f4 | ||
|
|
1eda5eb33a |
@@ -43,6 +43,7 @@ function createDefaultConfig(quality: string): Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getConfig(quality: string): Promise<Config> {
|
function getConfig(quality: string): Promise<Config> {
|
||||||
|
console.log(`Getting config for quality ${quality}`);
|
||||||
const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] });
|
const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] });
|
||||||
const collection = 'dbs/builds/colls/config';
|
const collection = 'dbs/builds/colls/config';
|
||||||
const query = {
|
const query = {
|
||||||
@@ -52,13 +53,13 @@ function getConfig(quality: string): Promise<Config> {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise<Config>((c, e) => {
|
return retry(() => new Promise<Config>((c, e) => {
|
||||||
client.queryDocuments(collection, query, { enableCrossPartitionQuery: true }).toArray((err, results) => {
|
client.queryDocuments(collection, query, { enableCrossPartitionQuery: true }).toArray((err, results) => {
|
||||||
if (err && err.code !== 409) { return e(err); }
|
if (err && err.code !== 409) { return e(err); }
|
||||||
|
|
||||||
c(!results || results.length === 0 ? createDefaultConfig(quality) : results[0] as any as Config);
|
c(!results || results.length === 0 ? createDefaultConfig(quality) : results[0] as any as Config);
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Asset {
|
interface Asset {
|
||||||
@@ -86,6 +87,7 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
|||||||
updateTries++;
|
updateTries++;
|
||||||
|
|
||||||
return new Promise<void>((c, e) => {
|
return new Promise<void>((c, e) => {
|
||||||
|
console.log(`Querying existing documents to update...`);
|
||||||
client.queryDocuments(collection, updateQuery, { enableCrossPartitionQuery: true }).toArray((err, results) => {
|
client.queryDocuments(collection, updateQuery, { enableCrossPartitionQuery: true }).toArray((err, results) => {
|
||||||
if (err) { return e(err); }
|
if (err) { return e(err); }
|
||||||
if (results.length !== 1) { return e(new Error('No documents')); }
|
if (results.length !== 1) { return e(new Error('No documents')); }
|
||||||
@@ -101,6 +103,7 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
|||||||
release.updates[platform] = type;
|
release.updates[platform] = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Replacing existing document with updated version`);
|
||||||
client.replaceDocument(release._self, release, err => {
|
client.replaceDocument(release._self, release, err => {
|
||||||
if (err && err.code === 409 && updateTries < 5) { return c(update()); }
|
if (err && err.code === 409 && updateTries < 5) { return c(update()); }
|
||||||
if (err) { return e(err); }
|
if (err) { return e(err); }
|
||||||
@@ -112,7 +115,8 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<void>((c, e) => {
|
return retry(() => new Promise<void>((c, e) => {
|
||||||
|
console.log(`Attempting to create document`);
|
||||||
client.createDocument(collection, release, err => {
|
client.createDocument(collection, release, err => {
|
||||||
if (err && err.code === 409) { return c(update()); }
|
if (err && err.code === 409) { return c(update()); }
|
||||||
if (err) { return e(err); }
|
if (err) { return e(err); }
|
||||||
@@ -120,7 +124,7 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
|||||||
console.log('Build successfully published.');
|
console.log('Build successfully published.');
|
||||||
c();
|
c();
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertContainer(blobService: azure.BlobService, quality: string): Promise<void> {
|
async function assertContainer(blobService: azure.BlobService, quality: string): Promise<void> {
|
||||||
@@ -188,7 +192,6 @@ async function publish(commit: string, quality: string, platform: string, type:
|
|||||||
console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Uploading blobs to Azure storage...');
|
console.log('Uploading blobs to Azure storage...');
|
||||||
|
|
||||||
await uploadBlob(blobService, quality, blobName, file);
|
await uploadBlob(blobService, quality, blobName, file);
|
||||||
@@ -247,6 +250,22 @@ async function publish(commit: string, quality: string, platform: string, type:
|
|||||||
await createOrUpdate(commit, quality, platform, type, release, asset, isUpdate);
|
await createOrUpdate(commit, quality, platform, type, release, asset, isUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RETRY_TIMES = 10;
|
||||||
|
async function retry<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
|
for (let run = 1; run <= RETRY_TIMES; run++) {
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (err) {
|
||||||
|
if (!/ECONNRESET/.test(err.message)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
console.log(`Caught error ${err} - ${run}/${RETRY_TIMES}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Retried too many times');
|
||||||
|
}
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
const commit = process.env['BUILD_SOURCEVERSION'];
|
const commit = process.env['BUILD_SOURCEVERSION'];
|
||||||
|
|
||||||
|
|||||||
@@ -134,11 +134,10 @@
|
|||||||
" if arc_admin_password != confirm_password:\n",
|
" if arc_admin_password != confirm_password:\n",
|
||||||
" sys.exit(f'Passwords do not match.')\n",
|
" sys.exit(f'Passwords do not match.')\n",
|
||||||
"\n",
|
"\n",
|
||||||
"os.environ[\"SPN_CLIENT_ID\"] = spn_client_id\n",
|
"os.environ[\"SPN_CLIENT_ID\"] = sp_client_id\n",
|
||||||
"os.environ[\"SPN_CLIENT_TENANTID\"] = spn_client_tenantid\n",
|
"os.environ[\"SPN_TENANT_ID\"] = sp_tenant_id\n",
|
||||||
"if \"AZDATA_NB_VAR_SPN_CLIENT_SECRET\" in os.environ:\n",
|
"if \"AZDATA_NB_VAR_SP_CLIENT_SECRET\" in os.environ:\n",
|
||||||
" os.environ[\"SPN_CLIENT_SECRET\"] = os.environ[\"AZDATA_NB_VAR_SPN_CLIENT_SECRET\"]\n",
|
" os.environ[\"SPN_CLIENT_SECRET\"] = os.environ[\"AZDATA_NB_VAR_SP_CLIENT_SECRET\"]\n",
|
||||||
" print(os.environ[\"SPN_CLIENT_TENANTID\"])\n",
|
|
||||||
"os.environ[\"SPN_AUTHORITY\"] = \"https://login.microsoftonline.com\""
|
"os.environ[\"SPN_AUTHORITY\"] = \"https://login.microsoftonline.com\""
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"name": "arc",
|
"name": "arc",
|
||||||
"displayName": "%arc.displayName%",
|
"displayName": "%arc.displayName%",
|
||||||
"description": "%arc.description%",
|
"description": "%arc.description%",
|
||||||
"version": "0.6.3",
|
"version": "0.6.5",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
"icon": "images/extension.png",
|
"icon": "images/extension.png",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "*",
|
"vscode": "*",
|
||||||
"azdata": ">=1.23.0"
|
"azdata": ">=1.25.0"
|
||||||
},
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onCommand:arc.connectToController",
|
"onCommand:arc.connectToController",
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.data.controller.data.controller.create.azureconfig.title%",
|
"title": "%arc.data.controller.create.azureconfig.title%",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"title": "%arc.data.controller.project.details.title%",
|
"title": "%arc.data.controller.project.details.title%",
|
||||||
@@ -218,7 +218,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "azure_locations",
|
"type": "azure_locations",
|
||||||
"label": "%arc.data.controller.arc.data.controller.location%",
|
"label": "%arc.data.controller.location%",
|
||||||
"defaultValue": "eastus",
|
"defaultValue": "eastus",
|
||||||
"required": true,
|
"required": true,
|
||||||
"locationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_LOCATION",
|
"locationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_LOCATION",
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"label": "%arc.data.controller.data.controller.connectivitymode.description%",
|
"label": "%arc.data.controller.connectivitymode.description%",
|
||||||
"labelWidth": "600px"
|
"labelWidth": "600px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -268,19 +268,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.data.controller.spnclient%",
|
"type": "readonly_text",
|
||||||
"variableName": "AZDATA_NB_VAR_SPN_CLIENT_ID",
|
"label": "%arc.data.controller.serviceprincipal.description%",
|
||||||
|
"labelWidth": "600px",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"text": "%arc.data.controller.readmore%",
|
||||||
|
"url": "https://docs.microsoft.com/azure/azure-arc/data/upload-metrics"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%arc.data.controller.spclientid%",
|
||||||
|
"description": "%arc.data.controller.spclientid.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SP_CLIENT_ID",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"required": true,
|
"required": true,
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
|
"placeHolder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
||||||
"value": "direct"
|
"value": "direct"
|
||||||
}
|
},
|
||||||
|
"validations" : [{
|
||||||
|
"type": "regex_match",
|
||||||
|
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
|
||||||
|
"description": "%arc.data.controller.spclientid.validation.description%"
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.data.controller.spnclientsecret%",
|
"label": "%arc.data.controller.spclientsecret%",
|
||||||
"variableName": "AZDATA_NB_VAR_SPN_CLIENT_SECRET",
|
"description": "%arc.data.controller.spclientsecret.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SP_CLIENT_SECRET",
|
||||||
"type": "password",
|
"type": "password",
|
||||||
"required": true,
|
"required": true,
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
@@ -290,38 +309,45 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.data.controller.spntenant%",
|
"label": "%arc.data.controller.sptenantid%",
|
||||||
"variableName": "AZDATA_NB_VAR_SPN_CLIENT_TENANTID",
|
"description": "%arc.data.controller.sptenantid.description%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_SP_TENANT_ID",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"required": true,
|
"required": true,
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
|
"placeHolder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
||||||
"value": "direct"
|
"value": "direct"
|
||||||
}
|
},
|
||||||
|
"validations" : [{
|
||||||
|
"type": "regex_match",
|
||||||
|
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
|
||||||
|
"description": "%arc.data.controller.sptenantid.validation.description%"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.data.controller.data.controller.create.controllerconfig.title%",
|
"title": "%arc.data.controller.create.controllerconfig.title%",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"title": "%arc.data.controller.data.controller.details.title%",
|
"title": "%arc.data.controller.details.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"label": "%arc.data.controller.data.controller.details.description%",
|
"label": "%arc.data.controller.details.description%",
|
||||||
"labelWidth": "600px"
|
"labelWidth": "600px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "%arc.data.controller.arc.data.controller.namespace%",
|
"label": "%arc.data.controller.namespace%",
|
||||||
"validations" : [{
|
"validations" : [{
|
||||||
"type": "regex_match",
|
"type": "regex_match",
|
||||||
"regex": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$",
|
"regex": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$",
|
||||||
"description": "%arc.data.controller.arc.data.controller.namespace.validation.description%"
|
"description": "%arc.data.controller.namespace.validation.description%"
|
||||||
}],
|
}],
|
||||||
"defaultValue": "arc",
|
"defaultValue": "arc",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -329,11 +355,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "%arc.data.controller.arc.data.controller.name%",
|
"label": "%arc.data.controller.name%",
|
||||||
"validations" : [{
|
"validations" : [{
|
||||||
"type": "regex_match",
|
"type": "regex_match",
|
||||||
"regex": "^[a-z0-9]([-.a-z0-9]{0,251}[a-z0-9])?$",
|
"regex": "^[a-z0-9]([-.a-z0-9]{0,251}[a-z0-9])?$",
|
||||||
"description": "%arc.data.controller.arc.data.controller.name.validation.description%"
|
"description": "%arc.data.controller.name.validation.description%"
|
||||||
}],
|
}],
|
||||||
"defaultValue": "arc-dc",
|
"defaultValue": "arc-dc",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -374,7 +400,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "%arc.data.controller.data.controller.create.summary.title%",
|
"title": "%arc.data.controller.create.summary.title%",
|
||||||
"isSummaryPage": true,
|
"isSummaryPage": true,
|
||||||
"fieldHeight": "16px",
|
"fieldHeight": "16px",
|
||||||
"sections": [
|
"sections": [
|
||||||
|
|||||||
@@ -20,18 +20,18 @@
|
|||||||
"arc.data.controller.kube.cluster.context": "Cluster context",
|
"arc.data.controller.kube.cluster.context": "Cluster context",
|
||||||
"arc.data.controller.cluster.config.profile.title": "Choose the config profile",
|
"arc.data.controller.cluster.config.profile.title": "Choose the config profile",
|
||||||
"arc.data.controller.cluster.config.profile": "Config profile",
|
"arc.data.controller.cluster.config.profile": "Config profile",
|
||||||
"arc.data.controller.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration",
|
"arc.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration",
|
||||||
"arc.data.controller.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.",
|
"arc.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.",
|
||||||
"arc.data.controller.data.controller.create.controllerconfig.title": "Controller Configuration",
|
"arc.data.controller.create.controllerconfig.title": "Controller Configuration",
|
||||||
"arc.data.controller.project.details.title": "Azure details",
|
"arc.data.controller.project.details.title": "Azure details",
|
||||||
"arc.data.controller.project.details.description": "Select the subscription to manage deployed resources and costs. Use resource groups like folders to organize and manage all your resources.",
|
"arc.data.controller.project.details.description": "Select the subscription to manage deployed resources and costs. Use resource groups like folders to organize and manage all your resources.",
|
||||||
"arc.data.controller.data.controller.details.title": "Data controller details",
|
"arc.data.controller.details.title": "Data controller details",
|
||||||
"arc.data.controller.data.controller.details.description": "Provide a namespace, name and storage class for your Azure Arc data controller. This name will be used to identify your Arc instance for remote management and monitoring.",
|
"arc.data.controller.details.description": "Provide a namespace, name and storage class for your Azure Arc data controller. This name will be used to identify your Arc instance for remote management and monitoring.",
|
||||||
"arc.data.controller.arc.data.controller.namespace": "Data controller namespace",
|
"arc.data.controller.namespace": "Data controller namespace",
|
||||||
"arc.data.controller.arc.data.controller.namespace.validation.description": "Namespace must consist of lower case alphanumeric characters or '-', start/end with an alphanumeric character, and be 63 characters or fewer in length.",
|
"arc.data.controller.namespace.validation.description": "Namespace must consist of lower case alphanumeric characters or '-', start/end with an alphanumeric character, and be 63 characters or fewer in length.",
|
||||||
"arc.data.controller.arc.data.controller.name": "Data controller name",
|
"arc.data.controller.name": "Data controller name",
|
||||||
"arc.data.controller.arc.data.controller.name.validation.description": "Name must consist of lower case alphanumeric characters, '-' or '.', start/end with an alphanumeric character and be 253 characters or less in length.",
|
"arc.data.controller.name.validation.description": "Name must consist of lower case alphanumeric characters, '-' or '.', start/end with an alphanumeric character and be 253 characters or less in length.",
|
||||||
"arc.data.controller.arc.data.controller.location": "Location",
|
"arc.data.controller.location": "Location",
|
||||||
"arc.data.controller.admin.account.title": "Administrator account",
|
"arc.data.controller.admin.account.title": "Administrator account",
|
||||||
"arc.data.controller.admin.account.name": "Data controller login",
|
"arc.data.controller.admin.account.name": "Data controller login",
|
||||||
"arc.data.controller.admin.account.password": "Password",
|
"arc.data.controller.admin.account.password": "Password",
|
||||||
@@ -39,10 +39,16 @@
|
|||||||
"arc.data.controller.connectivitymode": "Connectivity Mode",
|
"arc.data.controller.connectivitymode": "Connectivity Mode",
|
||||||
"arc.data.controller.direct": "Direct",
|
"arc.data.controller.direct": "Direct",
|
||||||
"arc.data.controller.indirect": "Indirect",
|
"arc.data.controller.indirect": "Indirect",
|
||||||
"arc.data.controller.spnclient": "SPN Client ID",
|
"arc.data.controller.serviceprincipal.description": "When deploying a controller in direct connected mode a Service Principal is required for uploading metrics to Azure. {0} about how to create this Service Principal and assign it the correct roles.",
|
||||||
"arc.data.controller.spnclientsecret": "SPN Client Secret",
|
"arc.data.controller.spclientid": "Service Principal Client ID",
|
||||||
"arc.data.controller.spntenant": "SPN Tenant ID",
|
"arc.data.controller.spclientid.description": "The Application (client) ID of the created Service Principal",
|
||||||
"arc.data.controller.data.controller.create.summary.title": "Review your configuration",
|
"arc.data.controller.spclientid.validation.description": "The client ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||||
|
"arc.data.controller.spclientsecret": "Service Principal Client Secret",
|
||||||
|
"arc.data.controller.spclientsecret.description": "The password generated during creation of the Service Principal",
|
||||||
|
"arc.data.controller.sptenantid": "Service Principal Tenant ID",
|
||||||
|
"arc.data.controller.sptenantid.description": "The Tenant ID of the Service Principal. This must be the same as the Tenant ID of the subscription selected to create this controller for.",
|
||||||
|
"arc.data.controller.sptenantid.validation.description": "The tenant ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||||
|
"arc.data.controller.create.summary.title": "Review your configuration",
|
||||||
"arc.data.controller.summary.arc.data.controller": "Azure Arc data controller",
|
"arc.data.controller.summary.arc.data.controller": "Azure Arc data controller",
|
||||||
"arc.data.controller.summary.estimated.cost.per.month": "Estimated cost per month",
|
"arc.data.controller.summary.estimated.cost.per.month": "Estimated cost per month",
|
||||||
"arc.data.controller.summary.arc.by.microsoft" : "by Microsoft",
|
"arc.data.controller.summary.arc.by.microsoft" : "by Microsoft",
|
||||||
@@ -65,7 +71,8 @@
|
|||||||
"arc.data.controller.summary.data.controller.namespace": "Data controller namespace",
|
"arc.data.controller.summary.data.controller.namespace": "Data controller namespace",
|
||||||
"arc.data.controller.summary.controller": "Controller",
|
"arc.data.controller.summary.controller": "Controller",
|
||||||
"arc.data.controller.summary.location": "Location",
|
"arc.data.controller.summary.location": "Location",
|
||||||
"arc.data.controller.arc.data.controller.agreement": "I accept {0} and {1}.",
|
"arc.data.controller.agreement": "I accept {0} and {1}.",
|
||||||
|
"arc.data.controller.readmore": "Read more",
|
||||||
"microsoft.agreement.privacy.statement":"Microsoft Privacy Statement",
|
"microsoft.agreement.privacy.statement":"Microsoft Privacy Statement",
|
||||||
"deploy.script.action":"Script to notebook",
|
"deploy.script.action":"Script to notebook",
|
||||||
"deploy.done.action":"Deploy",
|
"deploy.done.action":"Deploy",
|
||||||
|
|||||||
18
extensions/data-workspace/images/Open_existing_Project.svg
Normal file
18
extensions/data-workspace/images/Open_existing_Project.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<svg id="f43e4801-4f55-4d5f-909d-6739feccec92" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="b7240381-76e4-42bd-96fb-e944ca59ba0a" x1="50" y1="87.092" x2="50" y2="31.71" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#0078d4"/>
|
||||||
|
<stop offset="1" stop-color="#5ea0ef"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g>
|
||||||
|
<path d="M22.615,39.642h54.77V70.185a1.836,1.836,0,0,1-1.836,1.836h-51.1a1.836,1.836,0,0,1-1.836-1.836Z" fill="url(#b7240381-76e4-42bd-96fb-e944ca59ba0a)"/>
|
||||||
|
<path d="M24.451,27.979h51.1a1.836,1.836,0,0,1,1.836,1.836v9.827H22.615V29.783A1.835,1.835,0,0,1,24.451,27.979Z" fill="#8fc9f9"/>
|
||||||
|
<g>
|
||||||
|
<circle cx="27.869" cy="34.304" r="1.713" fill="#0078d4"/>
|
||||||
|
<circle cx="34.364" cy="34.304" r="1.713" fill="#0078d4"/>
|
||||||
|
<circle cx="40.86" cy="34.304" r="1.713" fill="#0078d4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M22.615,39.642h54.77V70.185a1.836,1.836,0,0,1-1.836,1.836h-51.1a1.836,1.836,0,0,1-1.836-1.836Z" fill="#feffff" opacity="0.07"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
16
extensions/data-workspace/images/Open_existing_Workspace.svg
Normal file
16
extensions/data-workspace/images/Open_existing_Workspace.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg id="a7873e7b-9149-4583-acd5-327a6cca8a74" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fa1a2df5-b243-4ccd-8fb8-af2408d310fc" x1="324" y1="432.822" x2="324" y2="483.565" gradientTransform="translate(-274 -408)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#5ea0ef"/>
|
||||||
|
<stop offset="1" stop-color="#0078d4"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g>
|
||||||
|
<path d="M24.225,30.625H75.807a1.612,1.612,0,0,1,1.644,1.483V67.892a1.579,1.579,0,0,1-1.644,1.483H24.225a1.612,1.612,0,0,1-1.676-1.483V32.172a1.612,1.612,0,0,1,1.674-1.547Z" fill="url(#fa1a2df5-b243-4ccd-8fb8-af2408d310fc)"/>
|
||||||
|
<path d="M45.5,36.008h9.284a.452.452,0,0,1,.452.452h0v9.671a.419.419,0,0,1-.419.419H45.7a.451.451,0,0,1-.451-.451V36.428a.418.418,0,0,1,.29-.42Z" fill="#fff"/>
|
||||||
|
<path d="M45.5,51.58h9.284a.487.487,0,0,1,.452.451V61.7a.42.42,0,0,1-.419.42H45.7a.452.452,0,0,1-.451-.452h0V52a.416.416,0,0,1,.29-.419ZM62.589,36.008h9.285a.452.452,0,0,1,.451.452h0v9.671a.419.419,0,0,1-.419.419H62.75A.451.451,0,0,1,62.3,46.1h0V36.428A.418.418,0,0,1,62.589,36.008Z" fill="#83b9f9"/>
|
||||||
|
<path d="M62.5,51.58h9.284a.487.487,0,0,1,.452.451V61.7a.42.42,0,0,1-.419.42H62.7a.452.452,0,0,1-.451-.452h0V52a.416.416,0,0,1,.29-.419Z" fill="#83b9f9"/>
|
||||||
|
<path d="M29.255,51.58h9.284a.487.487,0,0,1,.452.451V61.7a.42.42,0,0,1-.419.42H29.448A.452.452,0,0,1,29,61.67h0V52a.414.414,0,0,1,.29-.419Z" fill="#fff"/>
|
||||||
|
<path d="M29.255,36.008h9.284a.489.489,0,0,1,.452.452v9.671a.419.419,0,0,1-.419.419H29.448A.451.451,0,0,1,29,46.1h0V36.428a.416.416,0,0,1,.29-.42Z" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}.cls-2{fill:#212121;}</style></defs><title>file_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-2" d="M8.71,0,14,5.29V16H2V0ZM3,15H13V6H8V1H3ZM9,1.71V5h3.29Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 351 B |
@@ -1 +0,0 @@
|
|||||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>file_inverse_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-1" d="M8.71,0,14,5.29V16H2V0ZM3,15H13V6H8V1H3ZM9,1.71V5h3.29Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 335 B |
@@ -55,8 +55,9 @@ declare module 'dataworkspace' {
|
|||||||
*
|
*
|
||||||
* @param name Create a project
|
* @param name Create a project
|
||||||
* @param location the parent directory of the project
|
* @param location the parent directory of the project
|
||||||
|
* @param projectTypeId the identifier of the selected project type
|
||||||
*/
|
*/
|
||||||
createProject(name: string, location: vscode.Uri): Promise<vscode.Uri>;
|
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the supported project types
|
* Gets the supported project types
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
iconHeight: '50px',
|
iconHeight: '75px',
|
||||||
iconWidth: '50px',
|
iconWidth: '75px',
|
||||||
cardWidth: '170px',
|
cardWidth: '170px',
|
||||||
cardHeight: '170px',
|
cardHeight: '170px',
|
||||||
ariaLabel: constants.TypeTitle,
|
ariaLabel: constants.TypeTitle,
|
||||||
|
|||||||
@@ -22,16 +22,10 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
private _targetTypes = [
|
private _targetTypes = [
|
||||||
{
|
{
|
||||||
name: constants.Project,
|
name: constants.Project,
|
||||||
icon: {
|
icon: this.extensionContext.asAbsolutePath('images/Open_existing_Project.svg')
|
||||||
dark: this.extensionContext.asAbsolutePath('images/file_inverse.svg'),
|
|
||||||
light: this.extensionContext.asAbsolutePath('images/file.svg')
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
name: constants.Workspace,
|
name: constants.Workspace,
|
||||||
icon: {
|
icon: this.extensionContext.asAbsolutePath('images/Open_existing_Workspace.svg')
|
||||||
dark: this.extensionContext.asAbsolutePath('images/file_inverse.svg'), // temporary - still waiting for real icon from UX
|
|
||||||
light: this.extensionContext.asAbsolutePath('images/file.svg')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -97,8 +91,8 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
iconHeight: '50px',
|
iconHeight: '100px',
|
||||||
iconWidth: '50px',
|
iconWidth: '100px',
|
||||||
cardWidth: '170px',
|
cardWidth: '170px',
|
||||||
cardHeight: '170px',
|
cardHeight: '170px',
|
||||||
ariaLabel: constants.TypeTitle,
|
ariaLabel: constants.TypeTitle,
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
async createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> {
|
async createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> {
|
||||||
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const projectFile = await provider.createProject(name, location);
|
const projectFile = await provider.createProject(name, location, projectTypeId);
|
||||||
this.addProjectsToWorkspace([projectFile]);
|
this.addProjectsToWorkspace([projectFile]);
|
||||||
this._onDidWorkspaceProjectsChange.fire();
|
this._onDidWorkspaceProjectsChange.fire();
|
||||||
return projectFile;
|
return projectFile;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function createProjectProvider(projectTypes: IProjectType[]): IProjectPro
|
|||||||
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
||||||
return Promise.resolve(treeDataProvider);
|
return Promise.resolve(treeDataProvider);
|
||||||
},
|
},
|
||||||
createProject: (name: string, location: vscode.Uri): Promise<vscode.Uri> => {
|
createProject: (name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> => {
|
||||||
return Promise.resolve(location);
|
return Promise.resolve(location);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "3.0.0-release.59",
|
"version": "3.0.0-release.64",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp3.1.zip",
|
"Windows_86": "win-x86-netcoreapp3.1.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp3.1.zip",
|
"Windows_64": "win-x64-netcoreapp3.1.zip",
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { BookTreeItem } from './bookTreeItem';
|
|||||||
import { getPinnedNotebooks, setPinnedBookPathsInConfig, IBookNotebook } from '../common/utils';
|
import { getPinnedNotebooks, setPinnedBookPathsInConfig, IBookNotebook } from '../common/utils';
|
||||||
|
|
||||||
export interface IBookPinManager {
|
export interface IBookPinManager {
|
||||||
pinNotebook(notebook: BookTreeItem): boolean;
|
pinNotebook(notebook: BookTreeItem): Promise<boolean>;
|
||||||
unpinNotebook(notebook: BookTreeItem): boolean;
|
unpinNotebook(notebook: BookTreeItem): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PinBookOperation {
|
enum PinBookOperation {
|
||||||
@@ -39,15 +39,15 @@ export class BookPinManager implements IBookPinManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pinNotebook(notebook: BookTreeItem): boolean {
|
async pinNotebook(notebook: BookTreeItem): Promise<boolean> {
|
||||||
return this.isNotebookPinned(notebook.book.contentPath) ? false : this.updatePinnedBooks(notebook, PinBookOperation.Pin);
|
return this.isNotebookPinned(notebook.book.contentPath) ? false : await this.updatePinnedBooks(notebook, PinBookOperation.Pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
unpinNotebook(notebook: BookTreeItem): boolean {
|
async unpinNotebook(notebook: BookTreeItem): Promise<boolean> {
|
||||||
return this.updatePinnedBooks(notebook, PinBookOperation.Unpin);
|
return await this.updatePinnedBooks(notebook, PinBookOperation.Unpin);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePinnedBooks(notebook: BookTreeItem, operation: PinBookOperation) {
|
async updatePinnedBooks(notebook: BookTreeItem, operation: PinBookOperation): Promise<boolean> {
|
||||||
let modifiedPinnedBooks = false;
|
let modifiedPinnedBooks = false;
|
||||||
let bookPathToChange: string = notebook.book.contentPath;
|
let bookPathToChange: string = notebook.book.contentPath;
|
||||||
|
|
||||||
@@ -63,9 +63,8 @@ export class BookPinManager implements IBookPinManager {
|
|||||||
modifiedPinnedBooks = true;
|
modifiedPinnedBooks = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPinnedBookPathsInConfig(pinnedBooks);
|
await setPinnedBookPathsInConfig(pinnedBooks);
|
||||||
this.setPinnedSectionContext();
|
this.setPinnedSectionContext();
|
||||||
|
|
||||||
return modifiedPinnedBooks;
|
return modifiedPinnedBooks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
async pinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
async pinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
||||||
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
||||||
if (bookPathToUpdate) {
|
if (bookPathToUpdate) {
|
||||||
let pinStatusChanged = this.bookPinManager.pinNotebook(bookTreeItem);
|
let pinStatusChanged = await this.bookPinManager.pinNotebook(bookTreeItem);
|
||||||
if (pinStatusChanged) {
|
if (pinStatusChanged) {
|
||||||
bookTreeItem.contextValue = 'pinnedNotebook';
|
bookTreeItem.contextValue = 'pinnedNotebook';
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
async unpinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
async unpinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
||||||
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
||||||
if (bookPathToUpdate) {
|
if (bookPathToUpdate) {
|
||||||
let pinStatusChanged = this.bookPinManager.unpinNotebook(bookTreeItem);
|
let pinStatusChanged = await this.bookPinManager.unpinNotebook(bookTreeItem);
|
||||||
if (pinStatusChanged) {
|
if (pinStatusChanged) {
|
||||||
bookTreeItem.contextValue = 'savedNotebook';
|
bookTreeItem.contextValue = 'savedNotebook';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,13 +378,14 @@ function hasWorkspaceFolders(): boolean {
|
|||||||
return workspaceFolders && workspaceFolders.length > 0;
|
return workspaceFolders && workspaceFolders.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setPinnedBookPathsInConfig(pinnedNotebookPaths: IBookNotebook[]) {
|
export async function setPinnedBookPathsInConfig(pinnedNotebookPaths: IBookNotebook[]): Promise<void> {
|
||||||
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(notebookConfigKey);
|
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(notebookConfigKey);
|
||||||
let storeInWorspace: boolean = hasWorkspaceFolders();
|
let storeInWorspace: boolean = hasWorkspaceFolders();
|
||||||
|
|
||||||
config.update(pinnedBooksConfigKey, pinnedNotebookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
|
await config.update(pinnedBooksConfigKey, pinnedNotebookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IBookNotebook {
|
export interface IBookNotebook {
|
||||||
bookPath?: string;
|
bookPath?: string;
|
||||||
notebookPath: string;
|
notebookPath: string;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
|
|||||||
* @param location the parent directory
|
* @param location the parent directory
|
||||||
* @returns Uri of the newly created project file
|
* @returns Uri of the newly created project file
|
||||||
*/
|
*/
|
||||||
async createProject(name: string, location: vscode.Uri): Promise<vscode.Uri> {
|
async createProject(name: string, location: vscode.Uri, _: string): Promise<vscode.Uri> {
|
||||||
const projectFile = await this.projectController.createNewProject(name, location, true);
|
const projectFile = await this.projectController.createNewProject(name, location, true);
|
||||||
return vscode.Uri.file(projectFile);
|
return vscode.Uri.file(projectFile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "azuredatastudio",
|
"name": "azuredatastudio",
|
||||||
"version": "1.25.0",
|
"version": "1.25.2",
|
||||||
"distro": "ea65724e684f63e8185c71890f51290a8259e53e",
|
"distro": "e70265117090eff42a72ce17fa88b732b92e99b0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export class SerializationService implements ISerializationService {
|
|||||||
|
|
||||||
private createContinueRequest(serializationRequest: SerializeDataParams, index: number): azdata.SerializeDataContinueRequestParams {
|
private createContinueRequest(serializationRequest: SerializeDataParams, index: number): azdata.SerializeDataContinueRequestParams {
|
||||||
let numberOfRows = getBatchSize(serializationRequest.rowCount, index);
|
let numberOfRows = getBatchSize(serializationRequest.rowCount, index);
|
||||||
let rows = serializationRequest.getRowRange(index, serializationRequest.includeHeaders ?? false, numberOfRows);
|
let rows = serializationRequest.getRowRange(index, false, numberOfRows);
|
||||||
let isLastBatch = index + rows.length >= serializationRequest.rowCount;
|
let isLastBatch = index + rows.length >= serializationRequest.rowCount;
|
||||||
let continueSerializeRequest: azdata.SerializeDataContinueRequestParams = {
|
let continueSerializeRequest: azdata.SerializeDataContinueRequestParams = {
|
||||||
filePath: serializationRequest.filePath,
|
filePath: serializationRequest.filePath,
|
||||||
|
|||||||
@@ -66,8 +66,6 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 50px;
|
|
||||||
max-height: 50px;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
|||||||
@@ -8,6 +8,28 @@ import { URI } from 'vs/base/common/uri';
|
|||||||
import * as path from 'vs/base/common/path';
|
import * as path from 'vs/base/common/path';
|
||||||
import * as turndownPluginGfm from 'sql/workbench/contrib/notebook/browser/turndownPluginGfm';
|
import * as turndownPluginGfm from 'sql/workbench/contrib/notebook/browser/turndownPluginGfm';
|
||||||
|
|
||||||
|
// These replacements apply only to text. Here's how it's handled from Turndown:
|
||||||
|
// if (node.nodeType === 3) {
|
||||||
|
// replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
|
||||||
|
// }
|
||||||
|
const markdownReplacements = [
|
||||||
|
[/\\/g, '\\\\'],
|
||||||
|
[/\*/g, '\\*'],
|
||||||
|
[/^-/g, '\\-'],
|
||||||
|
[/^\+ /g, '\\+ '],
|
||||||
|
[/^(=+)/g, '\\$1'],
|
||||||
|
[/^(#{1,6}) /g, '\\$1 '],
|
||||||
|
[/`/g, '\\`'],
|
||||||
|
[/^~~~/g, '\\~~~'],
|
||||||
|
[/\[/g, '\\['],
|
||||||
|
[/\]/g, '\\]'],
|
||||||
|
[/^>/g, '\\>'],
|
||||||
|
[/_/g, '\\_'],
|
||||||
|
[/^(\d+)\. /g, '$1\\. '],
|
||||||
|
[/</g, '\\<'], // Added to ensure sample text like <hello> is escaped
|
||||||
|
[/>/g, '\\>'], // Added to ensure sample text like <hello> is escaped
|
||||||
|
];
|
||||||
|
|
||||||
export class HTMLMarkdownConverter {
|
export class HTMLMarkdownConverter {
|
||||||
private turndownService: TurndownService;
|
private turndownService: TurndownService;
|
||||||
|
|
||||||
@@ -21,7 +43,7 @@ export class HTMLMarkdownConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setTurndownOptions() {
|
private setTurndownOptions() {
|
||||||
this.turndownService.keep(['u', 'mark', 'style']);
|
this.turndownService.keep(['style']);
|
||||||
this.turndownService.use(turndownPluginGfm.gfm);
|
this.turndownService.use(turndownPluginGfm.gfm);
|
||||||
this.turndownService.addRule('pre', {
|
this.turndownService.addRule('pre', {
|
||||||
filter: 'pre',
|
filter: 'pre',
|
||||||
@@ -29,6 +51,22 @@ export class HTMLMarkdownConverter {
|
|||||||
return '\n```\n' + node.textContent + '\n```\n';
|
return '\n```\n' + node.textContent + '\n```\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.turndownService.addRule('mark', {
|
||||||
|
filter: 'mark',
|
||||||
|
replacement: (content, node) => {
|
||||||
|
return '<mark>' + content + '</mark>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.turndownService.addRule('underline', {
|
||||||
|
filter: ['u'],
|
||||||
|
replacement: (content, node, options) => {
|
||||||
|
if (!content.trim()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
content = addHighlightIfYellowBgExists(node, content);
|
||||||
|
return '<u>' + content + '</u>';
|
||||||
|
}
|
||||||
|
});
|
||||||
this.turndownService.addRule('caption', {
|
this.turndownService.addRule('caption', {
|
||||||
filter: 'caption',
|
filter: 'caption',
|
||||||
replacement: function (content, node) {
|
replacement: function (content, node) {
|
||||||
@@ -39,7 +77,6 @@ export class HTMLMarkdownConverter {
|
|||||||
this.turndownService.addRule('span', {
|
this.turndownService.addRule('span', {
|
||||||
filter: 'span',
|
filter: 'span',
|
||||||
replacement: function (content, node) {
|
replacement: function (content, node) {
|
||||||
let escapedText = escapeAngleBrackets(node.textContent);
|
|
||||||
// There are certain properties that either don't have equivalents in markdown or whose transformations
|
// There are certain properties that either don't have equivalents in markdown or whose transformations
|
||||||
// don't have actions defined in WYSIWYG yet. To unblock users, leaving these elements alone (including their child elements)
|
// don't have actions defined in WYSIWYG yet. To unblock users, leaving these elements alone (including their child elements)
|
||||||
// Note: the initial list was generated from our TSG Jupyter Book
|
// Note: the initial list was generated from our TSG Jupyter Book
|
||||||
@@ -75,7 +112,7 @@ export class HTMLMarkdownConverter {
|
|||||||
beginString = '<u>' + beginString;
|
beginString = '<u>' + beginString;
|
||||||
endString += '</u>';
|
endString += '</u>';
|
||||||
}
|
}
|
||||||
return beginString + escapedText + endString;
|
return beginString + content + endString;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.turndownService.addRule('img', {
|
this.turndownService.addRule('img', {
|
||||||
@@ -100,8 +137,6 @@ export class HTMLMarkdownConverter {
|
|||||||
const notebookLink = node.href ? URI.parse(node.href) : URI.file(node.title);
|
const notebookLink = node.href ? URI.parse(node.href) : URI.file(node.title);
|
||||||
const notebookFolder = this.notebookUri ? path.join(path.dirname(this.notebookUri.fsPath), path.sep) : '';
|
const notebookFolder = this.notebookUri ? path.join(path.dirname(this.notebookUri.fsPath), path.sep) : '';
|
||||||
let relativePath = findPathRelativeToContent(notebookFolder, notebookLink);
|
let relativePath = findPathRelativeToContent(notebookFolder, notebookLink);
|
||||||
node.innerText = escapeAngleBrackets(node.innerText);
|
|
||||||
content = escapeAngleBrackets(content);
|
|
||||||
if (relativePath) {
|
if (relativePath) {
|
||||||
return `[${node.innerText}](${relativePath})`;
|
return `[${node.innerText}](${relativePath})`;
|
||||||
}
|
}
|
||||||
@@ -115,7 +150,6 @@ export class HTMLMarkdownConverter {
|
|||||||
.replace(/^\n+/, '') // remove leading newlines
|
.replace(/^\n+/, '') // remove leading newlines
|
||||||
.replace(/\n+$/, '\n') // replace trailing newlines with just a single one
|
.replace(/\n+$/, '\n') // replace trailing newlines with just a single one
|
||||||
.replace(/\n/gm, '\n '); // indent
|
.replace(/\n/gm, '\n '); // indent
|
||||||
content = escapeAngleBrackets(content);
|
|
||||||
let prefix = options.bulletListMarker + ' ';
|
let prefix = options.bulletListMarker + ' ';
|
||||||
let parent = node.parentNode;
|
let parent = node.parentNode;
|
||||||
let nestedCount = 0;
|
let nestedCount = 0;
|
||||||
@@ -135,44 +169,22 @@ export class HTMLMarkdownConverter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.turndownService.addRule('p', {
|
|
||||||
filter: 'p',
|
|
||||||
replacement: function (content, node) {
|
|
||||||
let isAnchorElement: boolean = false;
|
|
||||||
node.childNodes.forEach(c => {
|
|
||||||
if (c.nodeType === Node.TEXT_NODE) {
|
|
||||||
c.nodeValue = escapeAngleBrackets(c.textContent);
|
|
||||||
} else if (c.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
c.innerText = escapeAngleBrackets(c.textContent);
|
|
||||||
if (c.nodeName === 'A') {
|
|
||||||
isAnchorElement = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (isAnchorElement) {
|
|
||||||
return content;
|
|
||||||
} else {
|
|
||||||
return '\n\n' + node.innerHTML.replace(/</gi, '<').replace(/>/gi, '>').replace(/ /gi, '') + '\n\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.turndownService.addRule('heading', {
|
this.turndownService.addRule('heading', {
|
||||||
filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
||||||
replacement: function (content, node, options) {
|
replacement: function (content, node, options) {
|
||||||
let hLevel = Number(node.nodeName.charAt(1));
|
let hLevel = Number(node.nodeName.charAt(1));
|
||||||
let escapedText = escapeAngleBrackets(content);
|
|
||||||
if (options.headingStyle === 'setext' && hLevel < 3) {
|
if (options.headingStyle === 'setext' && hLevel < 3) {
|
||||||
let underline = '#'.repeat(hLevel);
|
let underline = '#'.repeat(hLevel);
|
||||||
return '\n\n' + escapedText + '\n' + underline + '\n\n';
|
return '\n\n' + content + '\n' + underline + '\n\n';
|
||||||
} else {
|
} else {
|
||||||
return '\n\n' + '#'.repeat(hLevel) + ' ' + escapedText + '\n\n';
|
return '\n\n' + '#'.repeat(hLevel) + ' ' + content + '\n\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.turndownService.addRule('bold', {
|
this.turndownService.addRule('bold', {
|
||||||
filter: ['strong', 'b'],
|
filter: ['strong', 'b'],
|
||||||
replacement: function (content, node, options) {
|
replacement: function (content, node, options) {
|
||||||
content = escapeAngleBrackets(content);
|
content = addHighlightIfYellowBgExists(node, content);
|
||||||
if (!content.trim()) { return ''; }
|
if (!content.trim()) { return ''; }
|
||||||
return options.strongDelimiter + content + options.strongDelimiter;
|
return options.strongDelimiter + content + options.strongDelimiter;
|
||||||
}
|
}
|
||||||
@@ -180,7 +192,7 @@ export class HTMLMarkdownConverter {
|
|||||||
this.turndownService.addRule('italicize', {
|
this.turndownService.addRule('italicize', {
|
||||||
filter: ['em', 'i'],
|
filter: ['em', 'i'],
|
||||||
replacement: function (content, node, options) {
|
replacement: function (content, node, options) {
|
||||||
content = escapeAngleBrackets(content);
|
content = addHighlightIfYellowBgExists(node, content);
|
||||||
if (!content.trim()) { return ''; }
|
if (!content.trim()) { return ''; }
|
||||||
return options.emDelimiter + content + options.emDelimiter;
|
return options.emDelimiter + content + options.emDelimiter;
|
||||||
}
|
}
|
||||||
@@ -192,8 +204,7 @@ export class HTMLMarkdownConverter {
|
|||||||
|
|
||||||
return node.nodeName === 'CODE' && !isCodeBlock;
|
return node.nodeName === 'CODE' && !isCodeBlock;
|
||||||
},
|
},
|
||||||
replacement: function (content) {
|
replacement: function (content, node, options) {
|
||||||
content = escapeAngleBrackets(content);
|
|
||||||
if (!content.trim()) { return ''; }
|
if (!content.trim()) { return ''; }
|
||||||
|
|
||||||
let delimiter = '`';
|
let delimiter = '`';
|
||||||
@@ -209,9 +220,17 @@ export class HTMLMarkdownConverter {
|
|||||||
return delimiter + leadingSpace + content + trailingSpace + delimiter;
|
return delimiter + leadingSpace + content + trailingSpace + delimiter;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.turndownService.escape = escapeMarkdown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeMarkdown(text) {
|
||||||
|
return markdownReplacements.reduce(
|
||||||
|
(search, replacement) => search.replace(replacement[0], replacement[1]),
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function findPathRelativeToContent(notebookFolder: string, contentPath: URI | undefined): string {
|
export function findPathRelativeToContent(notebookFolder: string, contentPath: URI | undefined): string {
|
||||||
if (notebookFolder) {
|
if (notebookFolder) {
|
||||||
if (contentPath?.scheme === 'file') {
|
if (contentPath?.scheme === 'file') {
|
||||||
@@ -229,15 +248,9 @@ export function findPathRelativeToContent(notebookFolder: string, contentPath: U
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function escapeAngleBrackets(textContent: string): string {
|
export function addHighlightIfYellowBgExists(node, content: string): string {
|
||||||
let text: string = textContent;
|
if (node?.style?.backgroundColor === 'yellow') {
|
||||||
if (text.includes('<u>') || text.includes('<mark>') || (text.includes('style') && !text.includes('<style>'))) {
|
return '<mark>' + content + '</mark>';
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
let mapTags = { '<': '\\<', '>': '\\>' };
|
return content;
|
||||||
|
|
||||||
let escapedText = text.replace(/<|>/gi, function (matched) {
|
|
||||||
return mapTags[matched];
|
|
||||||
});
|
|
||||||
return escapedText;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,44 @@ export class TransformMarkdownAction extends Action {
|
|||||||
document.execCommand('formatBlock', false, 'H3');
|
document.execCommand('formatBlock', false, 'H3');
|
||||||
break;
|
break;
|
||||||
case MarkdownButtonType.HIGHLIGHT:
|
case MarkdownButtonType.HIGHLIGHT:
|
||||||
document.execCommand('hiliteColor', false, 'Yellow');
|
let selectionFocusNode = document.getSelection()?.focusNode;
|
||||||
|
// Find if element is wrapped in <mark></mark>
|
||||||
|
while (selectionFocusNode?.parentNode?.nodeName?.toLowerCase() && selectionFocusNode?.parentNode?.nodeName?.toLowerCase() !== 'mark') {
|
||||||
|
selectionFocusNode = selectionFocusNode.parentNode;
|
||||||
|
}
|
||||||
|
// Find if element is wrapped in <span background-color="yellow">
|
||||||
|
if (selectionFocusNode?.parentNode?.nodeName?.toLowerCase() !== 'mark') {
|
||||||
|
selectionFocusNode = document.getSelection()?.focusNode;
|
||||||
|
while (selectionFocusNode?.parentNode?.nodeName?.toLowerCase() && selectionFocusNode?.parentNode?.nodeName?.toLowerCase() !== 'span' && selectionFocusNode?.parentElement?.style?.backgroundColor !== 'yellow') {
|
||||||
|
selectionFocusNode = selectionFocusNode.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let nodeName = selectionFocusNode?.parentNode?.nodeName?.toLowerCase();
|
||||||
|
let backgroundColor = selectionFocusNode?.parentElement?.style?.backgroundColor;
|
||||||
|
if (nodeName === 'mark') {
|
||||||
|
let oldParent = selectionFocusNode.parentNode;
|
||||||
|
let newParent = selectionFocusNode.parentNode.parentNode;
|
||||||
|
let oldParentNextSibling = oldParent.nextSibling;
|
||||||
|
// Remove mark element, reparent
|
||||||
|
while (oldParent.childNodes.length > 0) {
|
||||||
|
// If no next sibling, then old parent was the final child node, so we can append
|
||||||
|
if (!oldParentNextSibling) {
|
||||||
|
newParent.appendChild(oldParent.firstChild);
|
||||||
|
} else {
|
||||||
|
newParent.insertBefore(oldParent.firstChild, oldParentNextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Empty span required to force an input so that HTML change is seen from text cell component
|
||||||
|
// This span doesn't have any effect on the markdown generated.
|
||||||
|
document.execCommand('formatBlock', false, 'span');
|
||||||
|
} else if (selectionFocusNode?.parentNode?.nodeName?.toLowerCase() === 'span' && backgroundColor === 'yellow') {
|
||||||
|
selectionFocusNode.parentElement.style.backgroundColor = '';
|
||||||
|
// Empty span required to force an input so that HTML change is seen from text cell component
|
||||||
|
// This span doesn't have any effect on the markdown generated.
|
||||||
|
document.execCommand('formatBlock', false, 'span');
|
||||||
|
} else {
|
||||||
|
document.execCommand('hiliteColor', false, 'Yellow');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case MarkdownButtonType.IMAGE:
|
case MarkdownButtonType.IMAGE:
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
@@ -760,22 +760,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigateToSection(id: string): void {
|
navigateToSection(id: string): void {
|
||||||
id = id.toLowerCase();
|
let section = this.getSectionElements().find(s => s.relativeUri && s.relativeUri.toLowerCase() === id.toLowerCase());
|
||||||
let chromeHeight: number = 0;
|
|
||||||
let elBody: HTMLElement = document.body;
|
|
||||||
let tabBar = elBody.querySelector('.title.tabs') as HTMLElement;
|
|
||||||
let actionBar = elBody.querySelector('.editor-toolbar.actionbar-container') as HTMLElement;
|
|
||||||
let section = this.getSectionElements().find(s => s.relativeUri && s.relativeUri.toLowerCase() === id);
|
|
||||||
if (section) {
|
if (section) {
|
||||||
// Scroll this section to the top of the header instead of just bringing header into view.
|
section.headerEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
if (tabBar && actionBar) {
|
|
||||||
chromeHeight = tabBar.scrollHeight + actionBar.scrollHeight;
|
|
||||||
}
|
|
||||||
let scrollTop: number = section.headerEl.getBoundingClientRect().top - (chromeHeight + 10);
|
|
||||||
(<HTMLElement>this.container.nativeElement).scrollTo({
|
|
||||||
top: scrollTop,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
section.headerEl.focus();
|
section.headerEl.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
min-height: 21px;
|
min-height: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notebookEditor .editor-toolbar .actions-container .action-item .notebook-button.masked-pseudo {
|
||||||
|
padding-right: 18px;
|
||||||
|
}
|
||||||
.notebookEditor .editor-toolbar .actions-container .action-item .notebook-button {
|
.notebookEditor .editor-toolbar .actions-container .action-item .notebook-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
|||||||
import { values } from 'vs/base/common/collections';
|
import { values } from 'vs/base/common/collections';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { assign } from 'vs/base/common/objects';
|
import { assign } from 'vs/base/common/objects';
|
||||||
|
import { equals } from 'vs/base/common/arrays';
|
||||||
@Component({
|
@Component({
|
||||||
selector: GridOutputComponent.SELECTOR,
|
selector: GridOutputComponent.SELECTOR,
|
||||||
template: `<div #output class="notebook-cellTable"></div>`
|
template: `<div #output class="notebook-cellTable"></div>`
|
||||||
@@ -119,6 +119,7 @@ export class GridOutputComponent extends AngularDisposable implements IMimeCompo
|
|||||||
}
|
}
|
||||||
if (!this._table) {
|
if (!this._table) {
|
||||||
let source = <IDataResource><any>this._bundleOptions.data[this.mimeType];
|
let source = <IDataResource><any>this._bundleOptions.data[this.mimeType];
|
||||||
|
reorderGridData(source);
|
||||||
let state = new GridTableState(0, 0);
|
let state = new GridTableState(0, 0);
|
||||||
this._table = this.instantiationService.createInstance(DataResourceTable, source, this.cellModel, this.cellOutput, state);
|
this._table = this.instantiationService.createInstance(DataResourceTable, source, this.cellModel, this.cellOutput, state);
|
||||||
let outputElement = <HTMLElement>this.output.nativeElement;
|
let outputElement = <HTMLElement>this.output.nativeElement;
|
||||||
@@ -146,6 +147,44 @@ export class GridOutputComponent extends AngularDisposable implements IMimeCompo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reorderGridData(source: IDataResource): void {
|
||||||
|
// Get Column Names list from the data resource schema
|
||||||
|
const columnNames: string[] = source.schema.fields.map(field => field.name);
|
||||||
|
// Check to see if data source is ordered properly based on schema
|
||||||
|
// Papermill executed notebooks with KQL for instance will organize the
|
||||||
|
// row data in alphabetical order as a result the outputted grid will be
|
||||||
|
// unordered and not based on the data resource schema
|
||||||
|
if (source.data.length > 0) {
|
||||||
|
let rowKeys = Object.keys(source.data[0]);
|
||||||
|
if (!equals(columnNames, rowKeys)) {
|
||||||
|
// SQL notebooks do not use columnName as key (instead use indices)
|
||||||
|
// Indicies indicate the row is ordered properly
|
||||||
|
// We must check the data to know if it is in index form
|
||||||
|
let notIndexOrderKeys = false;
|
||||||
|
for (let index = 0; index < rowKeys.length - 1; index++) {
|
||||||
|
// Index form (all numbers, start at 0 and increase by 1)
|
||||||
|
let value = Number(rowKeys[index]);
|
||||||
|
if (isNaN(value) || value !== index) {
|
||||||
|
// break if key is not a number or in index form
|
||||||
|
notIndexOrderKeys = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only reorder data that is not in index form
|
||||||
|
if (notIndexOrderKeys) {
|
||||||
|
source.data.forEach((row, index) => {
|
||||||
|
// Order each row based on the schema
|
||||||
|
let reorderedData = {};
|
||||||
|
for (let key of columnNames) {
|
||||||
|
reorderedData[key] = row[key];
|
||||||
|
}
|
||||||
|
source.data[index] = reorderedData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DataResourceTable extends GridTableBase<any> {
|
class DataResourceTable extends GridTableBase<any> {
|
||||||
|
|
||||||
private _gridDataProvider: DataResourceDataProvider;
|
private _gridDataProvider: DataResourceDataProvider;
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ rules['table'] = {
|
|||||||
// Ensure there are no blank lines
|
// Ensure there are no blank lines
|
||||||
content = content.replace('\n\n', '\n');
|
content = content.replace('\n\n', '\n');
|
||||||
// if the headings are empty, add border line and headings to keep table format
|
// if the headings are empty, add border line and headings to keep table format
|
||||||
if (node.tHead.innerText === '') {
|
if (node.tHead?.innerText === '') {
|
||||||
let emptyHeader = '\n\n|';
|
let emptyHeader = '\n\n|';
|
||||||
let border = '\n|';
|
let border = '\n|';
|
||||||
for (let i = 0; i < node.rows[0].childNodes.length; i++) {
|
for (let i = 0; i < node.rows[0].childNodes.length; i++) {
|
||||||
emptyHeader += ' |';
|
emptyHeader += ' |';
|
||||||
border += ' --- |'
|
border += ' --- |';
|
||||||
}
|
}
|
||||||
return emptyHeader + border + content + '\n\n';
|
return emptyHeader + border + content + '\n\n';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ suite('HTML Markdown Converter', function (): void {
|
|||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), 'Yes<u>Hello test</u>', 'Basic underline span no space failed');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), 'Yes<u>Hello test</u>', 'Basic underline span no space failed');
|
||||||
htmlString = '<h1>Yes<span style="text-decoration-line:underline; font-style:italic; font-weight:bold; background-color: yellow">Hello test</span></h1>';
|
htmlString = '<h1>Yes<span style="text-decoration-line:underline; font-style:italic; font-weight:bold; background-color: yellow">Hello test</span></h1>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '# Yes<u>_**<mark>Hello test</mark>**_</u>', 'Compound elements span failed');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '# Yes<u>_**<mark>Hello test</mark>**_</u>', 'Compound elements span failed');
|
||||||
|
htmlString = '<span style="background-color: yellow;"><b>Hello test</b></span>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '<mark>**Hello test**</mark>', 'Span with inner html not parsed correctly');
|
||||||
|
htmlString = '<b><span style="background-color: yellow;">Hello test</span></b>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**<mark>Hello test</mark>**', 'Span inside bold tag parsed correctly');
|
||||||
htmlString = '<span style="color: orangered">Hello test</span>';
|
htmlString = '<span style="color: orangered">Hello test</span>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), htmlString, 'Span with color style should not be altered');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), htmlString, 'Span with color style should not be altered');
|
||||||
htmlString = '<span style="font-size: 10.0pt">Hello test</span>';
|
htmlString = '<span style="font-size: 10.0pt">Hello test</span>';
|
||||||
@@ -158,9 +162,9 @@ suite('HTML Markdown Converter', function (): void {
|
|||||||
|
|
||||||
test('Should keep < > tag', () => {
|
test('Should keep < > tag', () => {
|
||||||
htmlString = '<test>';
|
htmlString = '<test>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '<test>', 'Non-HTML tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '\\<test\\>', 'Non-HTML tag test failed to escape');
|
||||||
htmlString = '<test><span style="background:red">message</span><test>';
|
htmlString = '<test><span style="background:red">message</span><test>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '<test><span style="background:red">message</span><test>', 'Non-HTML tag inside span tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '\\<test\\><span style="background:red">message</span>\\<test\\>', 'Non-HTML tag inside span tag test failed to escape');
|
||||||
htmlString = '<h1><test><h1>';
|
htmlString = '<h1><test><h1>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '# \\<test\\>', 'Non-HTML tag inside H1 tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '# \\<test\\>', 'Non-HTML tag inside H1 tag test failed to escape');
|
||||||
htmlString = '<h2><test><h2>';
|
htmlString = '<h2><test><h2>';
|
||||||
@@ -174,19 +178,19 @@ suite('HTML Markdown Converter', function (): void {
|
|||||||
htmlString = '<em><Italicize test></em>';
|
htmlString = '<em><Italicize test></em>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '_\\<Italicize test\\>_', 'Basic italicize non-HTML tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_\\<Italicize test\\>_', 'Basic italicize non-HTML tag test failed to escape');
|
||||||
htmlString = '<u><Underline_test></u> ';
|
htmlString = '<u><Underline_test></u> ';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '<u><Underline_test></u>', 'Basic underline non-HTML tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '<u>\\<Underline\\_test\\></u>', 'Basic underline non-HTML tag test failed to escape');
|
||||||
htmlString = '<ul><li><test></li></ul>';
|
htmlString = '<ul><li><test></li></ul>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '- \\<test\\>', 'Basic unordered list non-HTML tag item test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '- \\<test\\>', 'Basic unordered list non-HTML tag item test failed to escape');
|
||||||
htmlString = '<ol><li><test></li></ol>';
|
htmlString = '<ol><li><test></li></ol>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '1. \\<test\\>', 'Basic ordered list non-HTML tag item test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '1. \\<test\\>', 'Basic ordered list non-HTML tag item test failed to escape');
|
||||||
htmlString = '<mark><test></mark>';
|
htmlString = '<mark><test></mark>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '<mark><test></mark>', 'Basic highlighting Non-HTML tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '<mark>\\<test\\></mark>', 'Basic highlighting Non-HTML tag test failed to escape');
|
||||||
htmlString = '<mark><h1><test></h1></mark>';
|
htmlString = '<mark><h1><test></h1></mark>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '<mark><h1><test></h1></mark>', 'Non-HTML tag inside multiple html tags test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '<mark>\n\n# \\<test\\>\n\n</mark>', 'Non-HTML tag inside multiple html tags test failed to escape');
|
||||||
htmlString = '<p><style></p>';
|
htmlString = '<p><style></p>';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '\\<style\\>', 'Style tag as a non-HTML tag test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '\\<style\\>', 'Style tag as a non-HTML tag test failed to escape');
|
||||||
htmlString = '<test> <u>Underlined Text style</u> end';
|
htmlString = '<test> <u>Underlined Text style</u> end';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), '<test> <u>Underlined Text style</u> end', 'Non-HTML tag outside with style and underline test failed to escape');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '\\<test\\> <u>Underlined Text style</u> end', 'Non-HTML tag outside with style and underline test failed to escape');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,4 +208,54 @@ suite('HTML Markdown Converter', function (): void {
|
|||||||
htmlString = '<table>\n<thead>\n<tr>\n<th>Test</th>\n<th>Test</th>\n<th>Test</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n</tbody></table>\n';
|
htmlString = '<table>\n<thead>\n<tr>\n<th>Test</th>\n<th>Test</th>\n<th>Test</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n</tbody></table>\n';
|
||||||
assert.equal(htmlMarkdownConverter.convert(htmlString), `| Test | Test | Test |\n| --- | --- | --- |\n| test | test | test |\n| test | test | test |\n| test | test | test |\n| test | test | test |`, 'Table with header failed');
|
assert.equal(htmlMarkdownConverter.convert(htmlString), `| Test | Test | Test |\n| --- | --- | --- |\n| test | test | test |\n| test | test | test |\n| test | test | test |\n| test | test | test |`, 'Table with header failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should transform table with no thead', () => {
|
||||||
|
htmlString = '<table>\n<tr>\n<th>Test</th>\n<th>Test</th>\n<th>Test</th>\n</tr>\n<tbody><tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n<tr>\n<td>test</td>\n<td>test</td>\n<td>test</td>\n</tr>\n</tbody></table>\n';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), `| Test | Test | Test |\n| --- | --- | --- |\n| test | test | test |\n| test | test | test |\n| test | test | test |\n| test | test | test |`, 'Table with no thead failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should transform <b> and <strong> tags', () => {
|
||||||
|
htmlString = '<b>test string</b>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**test string**', 'Basic bold test failed');
|
||||||
|
htmlString = '<b style="background-color: yellow">test string</b>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**<mark>test string</mark>**', 'Highlight bold test failed');
|
||||||
|
htmlString = '<b style="background-color: yellow"><i>test string</i></b>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**<mark>_test string_</mark>**', 'Highlight bold italic test failed');
|
||||||
|
htmlString = '<b style="blah: nothing">test string</b>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**test string**', 'Incorrect style bold test failed');
|
||||||
|
htmlString = '<strong>test string</strong>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**test string**', 'Basic strong test failed');
|
||||||
|
htmlString = '<strong style="background-color: yellow">test string</strong>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**<mark>test string</mark>**', 'Highlight strong test failed');
|
||||||
|
htmlString = '<strong style="blah: nothing">test string</strong>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**test string**', 'Incorrect style strong test failed');
|
||||||
|
});
|
||||||
|
test('Should transform <i> and <em> tags', () => {
|
||||||
|
htmlString = '<i>test string</i>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_test string_', 'Basic italic test failed');
|
||||||
|
htmlString = '<p><i>test string</i></p>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_test string_', 'Basic italic test failed');
|
||||||
|
htmlString = '<i style="background-color: yellow">test string</i>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_<mark>test string</mark>_', 'Highlight italic test failed');
|
||||||
|
htmlString = '<i style="background-color: yellow"><b>test string</b></i>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_<mark>**test string**</mark>_', 'Highlight italic bold test failed');
|
||||||
|
htmlString = '<i style="blah: nothing">test string</i>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_test string_', 'Incorrect style italic test failed');
|
||||||
|
htmlString = '<em>test string</em>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_test string_', 'Basic em test failed');
|
||||||
|
htmlString = '<em style="background-color: yellow">test string</em>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_<mark>test string</mark>_', 'Highlight em test failed');
|
||||||
|
htmlString = '<em style="blah: nothing">test string</em>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_test string_', 'Incorrect style em test failed');
|
||||||
|
htmlString = '<em style="background-color: yellow"><b>test string</b></em>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '_<mark>**test string**</mark>_', 'Highlight em bold test failed');
|
||||||
|
});
|
||||||
|
test('Should transform <u> when necessary', () => {
|
||||||
|
htmlString = '<u>test string</u>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), htmlString, 'Basic underline test failed');
|
||||||
|
htmlString = '<u style="background-color: yellow">test string</u>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '<u><mark>test string</mark></u>', 'Highlight underline test failed');
|
||||||
|
htmlString = '<b><u style="background-color: yellow">test string</u></b>';
|
||||||
|
assert.equal(htmlMarkdownConverter.convert(htmlString), '**<u><mark>test string</mark></u>**', 'Underline as inner element failed');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -157,17 +157,16 @@ export class CopyQueryWithResultsKeyboardAction extends Action {
|
|||||||
let resultSummary = queryRunner.batchSets[0].resultSetSummaries[i];
|
let resultSummary = queryRunner.batchSets[0].resultSetSummaries[i];
|
||||||
let result = await queryRunner.getQueryRows(0, resultSummary.rowCount, resultSummary.batchId, resultSummary.id);
|
let result = await queryRunner.getQueryRows(0, resultSummary.rowCount, resultSummary.batchId, resultSummary.id);
|
||||||
let tableHeaders = resultSummary.columnInfo.map((col, i) => (col.columnName));
|
let tableHeaders = resultSummary.columnInfo.map((col, i) => (col.columnName));
|
||||||
let htmlTableHeaders = resultSummary.columnInfo.map((col, i) => (`<th style="border:solid black 1.0pt; whiteSpace:nowrap">${escape(col.columnName)}</th>`));
|
let htmlTableHeaders = `<thead><tr style="background-color:DarkGray">${resultSummary.columnInfo.map((col, i) => (`<th style="border:1.0px solid black;padding:3pt;font-size:9pt;font-weight: bold;">${escape(col.columnName)}</th>`)).join('')}</tr></thead>`;
|
||||||
let copyString = '\n';
|
let copyString = '\n';
|
||||||
let htmlCopyString = '<tr>';
|
let htmlCopyString = '';
|
||||||
|
|
||||||
for (let rowEntry of result.rows) {
|
for (let rowEntry of result.rows) {
|
||||||
|
htmlCopyString = htmlCopyString + '<tr>';
|
||||||
for (let colIdx = 0; colIdx < rowEntry.length; colIdx++) {
|
for (let colIdx = 0; colIdx < rowEntry.length; colIdx++) {
|
||||||
let value = rowEntry[colIdx].displayValue;
|
let value = rowEntry[colIdx].displayValue;
|
||||||
if (value) {
|
copyString = `${copyString}${value}\t`;
|
||||||
copyString = `${copyString}${value}\t`;
|
htmlCopyString = `${htmlCopyString}<td style="border:1.0px solid black;padding:3pt;font-size:9pt;">${escape(value)}</td>`;
|
||||||
htmlCopyString = `${htmlCopyString}<td style="border:solid black 1.0pt;white-space:nowrap">${escape(value)}</td>`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Removes the tab seperator from the end of a row
|
// Removes the tab seperator from the end of a row
|
||||||
copyString = copyString.slice(0, -1 * '\t'.length) + '\n';
|
copyString = copyString.slice(0, -1 * '\t'.length) + '\n';
|
||||||
@@ -177,7 +176,7 @@ export class CopyQueryWithResultsKeyboardAction extends Action {
|
|||||||
allResults = `${allResults}${tableHeaders.join('\t')}${copyString}\n`;
|
allResults = `${allResults}${tableHeaders.join('\t')}${copyString}\n`;
|
||||||
allHtmlResults = `${allHtmlResults}<div><br/><br/>
|
allHtmlResults = `${allHtmlResults}<div><br/><br/>
|
||||||
<table cellPadding="5" cellSpacing="1" style="border:1;border-color:Black;font-family:Segoe UI;font-size:12px;border-collapse:collapse">
|
<table cellPadding="5" cellSpacing="1" style="border:1;border-color:Black;font-family:Segoe UI;font-size:12px;border-collapse:collapse">
|
||||||
<tr style="background-color:DarkGray">${htmlTableHeaders.join('')}</tr>${htmlCopyString}
|
${htmlTableHeaders}${htmlCopyString}
|
||||||
</table></div>`;
|
</table></div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user