├── images
└── whats-new-bookmarks.png
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ ├── question.md
│ └── bug_report.md
├── CONTRIBUTING.md
├── LICENSE.md
├── src
├── ContentProvider.ts
├── Manager.ts
└── PageBuilder.ts
├── ui
├── whats-new.html
└── main.css
└── README.md
/images/whats-new-bookmarks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alefragnani/vscode-whats-new/HEAD/images/whats-new-bookmarks.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: alefragnani
2 | patreon: alefragnani
3 | custom: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=EP57F3B6FXKTU&lc=US&item_name=Alessandro%20Fragnani&item_number=vscode%20extensions¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for the module
4 | title: "[FEATURE] - "
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question about the module
4 | title: "[QUESTION] - "
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help the module improve
4 | title: "[BUG] - "
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 |
13 | **Environment/version**
14 |
15 | - Extension version:
16 | - VSCode version:
17 | - OS version:
18 |
19 | **Steps to reproduce**
20 |
21 | 1.
22 | 2.
23 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | First off all, thank you for taking the time to contribute!
4 |
5 | When contributing to this project, please first discuss the changes you wish to make via an issue before making changes.
6 |
7 | ## Your First Code Contribution
8 |
9 | Unsure where to begin contributing? You can start by looking through the [`help wanted`](https://github.com/alefragnani/vscode-whats-new/labels/help%20wanted) issues.
10 |
11 | ### Getting the code
12 |
13 | This repo is intended to be used as a `git submodule`. So, the best approach is to use it this way.
14 |
15 | Follow the [README](./README.md) instructions on how to use this submodule on your extension.
16 |
17 | ## Submitting a Pull Request
18 |
19 | Be sure your branch is up to date (relative to `master`) and submit your PR. Also add reference to the issue the PR refers to.
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Alessandro Fragnani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/ContentProvider.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Alessandro Fragnani. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | // common
7 | export interface Image {
8 | src: string;
9 | width: number;
10 | height: number;
11 | }
12 |
13 | // header
14 | export interface Header {
15 | logo: Image;
16 | message: string;
17 | }
18 |
19 | // changelog
20 | export enum ChangeLogKind {
21 | NEW = "NEW",
22 | CHANGED = "CHANGED",
23 | FIXED = "FIXED",
24 | VERSION = "VERSION",
25 | INTERNAL = "INTERNAL"
26 | }
27 |
28 | export enum IssueKind {
29 | Issue = "Issue",
30 | PR = "PR"
31 | }
32 |
33 | export interface ChangeLogIssue {
34 | message: string;
35 | id: number;
36 | kind: IssueKind;
37 | kudos?: string;
38 | }
39 |
40 | export interface ChangeLogVersion {
41 | releaseNumber: string;
42 | releaseDate: string;
43 | }
44 |
45 | export interface ChangeLogItem {
46 | kind: ChangeLogKind;
47 | detail: ChangeLogIssue | ChangeLogVersion | string ;
48 | }
49 |
50 | export interface Image {
51 | light: string;
52 | dark: string
53 | }
54 |
55 | // sponsor
56 | export interface Sponsor {
57 | title: string;
58 | link: string;
59 | image: Image;
60 | width: number;
61 | message: string;
62 | extra: string;
63 | }
64 |
65 | export interface SupportChannel {
66 | title: string;
67 | link: string;
68 | message: string;
69 | }
70 |
71 | export interface ContentProvider {
72 | provideHeader(logoUrl: string): Header;
73 | provideChangeLog(): ChangeLogItem[];
74 | provideSupportChannels(): SupportChannel[];
75 | }
76 |
77 | export interface SponsorProvider {
78 | provideSponsors(): Sponsor[];
79 | }
80 |
81 | export interface SocialMedia {
82 | link: string;
83 | title: string;
84 | }
85 |
86 | export interface SocialMediaProvider {
87 | provideSocialMedias(): SocialMedia[];
88 | }
--------------------------------------------------------------------------------
/ui/whats-new.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 | What's New in ${extensionDisplayName} ${extensionVersion}
35 |
40 |
41 |
42 |
Need Help?
43 |
51 |
52 |
53 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What's New submodule
2 |
3 | This submodule is used to display a **What's New** page on VS Code extensions. It has a simple (but yet effective) UI, optimized to display all the information in a single page.
4 |
5 | 
6 |
7 | ## Usage
8 |
9 | ### Adding
10 |
11 | You just need to add a `submodule` reference to your Git repo
12 |
13 | ```
14 | git submodule add https://github.com/alefragnani/vscode-whats-new.git
15 |
16 | ```
17 |
18 | ### Coding
19 |
20 | This submodule is based on a `Provider` pattern (similar to some pieces of VS Code), and you need to implement at least the `ContentProvider` in order to display the main content of the page, like the Header (logo and message) and Changelog.
21 |
22 | There are other two providers available, for specific contents:
23 |
24 | * `SocialMediaProvider`": Add links to your social medias
25 | * `SponsorProvider`: Add references (logos and messages) to your sponsors.
26 |
27 | ```ts
28 |
29 | // provide the data
30 | import { ChangeLogItem,
31 | ChangeLogKind,
32 | ContentProvider,
33 | SocialMediaProvider,
34 | SponsorProvider,
35 | Header,
36 | Image
37 | } from "../../vscode-whats-new/src/ContentProvider";
38 |
39 | export class BookmarksContentProvider implements ContentProvider {
40 |
41 | provideHeader(logoUrl: string): Header {
42 | return {logo: {src: logoUrl, height: 50, width: 50},
43 | message: `Bookmarks helps you to navigate in your code, moving
44 | between important positions easily and quickly. No more need
45 | to search for code . It also supports a set of selection
46 | commands, which allows you to select bookmarked lines and regions between
47 | lines.`};
48 | }
49 |
50 | provideChangeLog(): ChangeLogItem[] {
51 | let changeLog: ChangeLogItem[] = [];
52 | changeLog.push({ kind: ChangeLogKind.VERSION, detail: { releaseNumber: "12.1.0", releaseDate: "December 2020" } });
53 | changeLog.push({
54 | kind: ChangeLogKind.NEW,
55 | detail: {
56 | message: "Support submenu for editor commands",
57 | id: 351,
58 | kind: IssueKind.Issue
59 | }
60 | });
61 | changeLog.push({
62 | kind: ChangeLogKind.CHANGED,
63 | detail: {
64 | message: "Setting bookmarks.navigateThroughAllFiles is now true by default",
65 | id: 102,
66 | kind: IssueKind.Issue
67 | }
68 | });
69 | changeLog.push({
70 | kind: ChangeLogKind.INTERNAL,
71 | detail: {
72 | message: "Remove unnecessary files from extension package",
73 | id: 355,
74 | kind: IssueKind.Issue
75 | }
76 | });
77 | }
78 | }
79 |
80 | export class BookmarksSocialMediaProvider implements SocialMediaProvider {
81 | public provideSocialMedias() {
82 | return [{
83 | title: "Follow me on Twitter",
84 | link: "https://www.twitter.com/alefragnani"
85 | }];
86 | }
87 | }
88 |
89 | export class BookmarksSponsorProvider implements SponsorProvider {
90 | public provideSponsors(): Sponsor[] {
91 | const sponsors: Sponsor[] = [];
92 | const sponsorCodeStream: Sponsor = {
93 | title: "Learn more about Codestream",
94 | link: "https://sponsorlink.codestream.com/?utm_source=vscmarket&utm_campaign=bookmarks&utm_medium=banner",
95 | image: {
96 | light: "https://alt-images.codestream.com/codestream_logo_bookmarks.png",
97 | dark: "https://alt-images.codestream.com/codestream_logo_bookmarks.png"
98 | },
99 | width: 52,
100 | message: `Eliminate context switching and costly distractions.
101 | Create and merge PRs and perform code reviews from inside your
102 | IDE while using jump-to-definition, your keybindings, and other IDE favorites.
`,
103 | extra:
104 | `
105 | Learn more `
106 | };
107 | sponsors.push(sponsorCodeStream);
108 | return sponsors;
109 | }
110 | }
111 |
112 | // register the providers
113 | const provider = new BookmarksContentProvider();
114 | const viewer = new WhatsNewManager(context)
115 | .registerContentProvider("alefragnani", "bookmarks", provider)
116 | .registerSocialMediaProvider(new BookmarksSocialMediaProvider())
117 | .registerSponsorProvider(new BookmarksSponsorProvider());
118 |
119 | // show the page (if necessary)
120 | await viewer.showPageInActivation();
121 |
122 | // register the additional command (not really necessary, unless you want a command registered in your extension)
123 | context.subscriptions.push(vscode.commands.registerCommand("bookmarks.whatsNew", () => viewer.showPage()));
124 | ```
125 | ## Features
126 |
127 | ### Detects version updates
128 |
129 | It follows [SEMVER - Semantic Versioning](https://www.semver.org) to detect **Major**, **Minor** and **Patch** versions. The **What's New** page will only be displayed when a **Major** or **Minor** update occurs. **Patches** are updated silently.
130 |
131 | ### Template Based
132 |
133 | I don't have to deal with HTML or CSS on my extensions anymore. I just have to _provide_ the relevant information and the HTML page is automatically generated/updated.
134 |
135 | ## Inspiration
136 |
137 | The idea came from the [GitLens extension](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) by @eamodio (big thanks to Eric Amodio :claps:). Based on pieces of its welcome page, I created this template based engine, to be able to use in any of my extensions, with minimum effort.
138 |
139 | # License
140 |
141 | [MIT](LICENSE.md) © Alessandro Fragnani
--------------------------------------------------------------------------------
/src/Manager.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Alessandro Fragnani. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import * as semver from "semver";
7 | import * as vscode from "vscode";
8 | import { Uri, Webview } from "vscode";
9 | import { ContentProvider, SocialMediaProvider, SponsorProvider } from "./ContentProvider";
10 | import { WhatsNewPageBuilder } from "./PageBuilder";
11 |
12 | export class WhatsNewManager {
13 |
14 | private publisher!: string;
15 | private extensionName!: string;
16 | private context: vscode.ExtensionContext;
17 | private contentProvider!: ContentProvider;
18 | private socialMediaProvider!: SocialMediaProvider | undefined;
19 | private sponsorProvider: SponsorProvider | undefined;
20 |
21 | private extension!: vscode.Extension;
22 | private versionKey!: string;
23 |
24 | constructor(context: vscode.ExtensionContext) {
25 | this.context = context;
26 | }
27 |
28 | private isRunningOnCodespaces(): boolean {
29 | return vscode.env.remoteName?.toLocaleLowerCase() === 'codespaces';
30 | }
31 |
32 | private isRunningOnGitpod(): boolean {
33 | return !!process.env.GITPOD_WORKSPACE_ID;
34 | }
35 |
36 | public registerContentProvider(publisher: string, extensionName: string, contentProvider: ContentProvider): WhatsNewManager {
37 | this.publisher = publisher;
38 | this.extensionName = extensionName
39 | this.contentProvider = contentProvider;
40 | this.versionKey = `${this.extensionName}.version`;
41 |
42 | this.context.globalState.setKeysForSync([this.versionKey]);
43 |
44 | return this;
45 | }
46 |
47 | public registerSocialMediaProvider(socialMediaProvider: SocialMediaProvider): WhatsNewManager {
48 | this.socialMediaProvider = socialMediaProvider;
49 | return this;
50 | }
51 |
52 | public registerSponsorProvider(sponsorProvider: SponsorProvider): WhatsNewManager {
53 | this.sponsorProvider = sponsorProvider;
54 | return this;
55 | }
56 |
57 | public async showPageInActivation() {
58 | // load data from extension manifest
59 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
60 | this.extension = vscode.extensions.getExtension(`${this.publisher}.${this.extensionName}`)!;
61 |
62 | const previousExtensionVersion = this.context.globalState.get(this.versionKey);
63 |
64 | await this.showPageIfCurrentVersionIsGreaterThanPrevisouVersion(this.extension.packageJSON.version, previousExtensionVersion);
65 | }
66 |
67 | public async showPage() {
68 |
69 | // Create and show panel
70 | const panel = vscode.window.createWebviewPanel(`${this.extensionName}.whatsNew`,
71 | `What's New in ${this.extension.packageJSON.displayName}`, vscode.ViewColumn.One, { enableScripts: true });
72 |
73 | // Path to HTML
74 | const onDiskPath = vscode.Uri.joinPath(this.context.extensionUri, "vscode-whats-new", "ui", "whats-new.html");
75 |
76 | // Path to CSS
77 | const cssPathOnDisk = vscode.Uri.joinPath(this.context.extensionUri, "vscode-whats-new", "ui", "main.css");
78 |
79 | // Path to Logo
80 | const logoPathOnDisk = vscode.Uri.joinPath(this.context.extensionUri, "images", `vscode-${this.extensionName.toLowerCase()}-logo-readme.png`);
81 |
82 | panel.webview.html = await this.getWebviewContentLocal(panel.webview, onDiskPath, cssPathOnDisk, logoPathOnDisk);
83 | }
84 |
85 | public async showPageIfVersionDiffers(currentVersion: string, previousVersion: string | undefined) {
86 |
87 | if (previousVersion) {
88 | const differs: semver.ReleaseType | null = semver.diff(currentVersion, previousVersion);
89 |
90 | // only "patch" should be suppressed
91 | if (!differs || differs === "patch") {
92 | return;
93 | }
94 | }
95 |
96 | // "major", "minor"
97 | this.context.globalState.update(this.versionKey, currentVersion);
98 |
99 | //
100 | if (this.isRunningOnCodespaces() || this.isRunningOnGitpod()) {
101 | return;
102 | }
103 |
104 | await this.showPage();
105 | }
106 |
107 | public async showPageIfCurrentVersionIsGreaterThanPrevisouVersion(currentVersion: string, previousVersion: string | undefined) {
108 | if (previousVersion) {
109 | const differs: semver.ReleaseType | null = semver.diff(currentVersion, previousVersion);
110 | const isGreaterThanPreviousVersion = semver.gt(currentVersion, previousVersion);
111 |
112 | // only "patch" should be suppressed
113 | if (!differs || differs === "patch" || !isGreaterThanPreviousVersion) {
114 | return;
115 | }
116 | }
117 |
118 | // "major", "minor"
119 | this.context.globalState.update(this.versionKey, currentVersion);
120 |
121 | //
122 | if (this.isRunningOnCodespaces() || this.isRunningOnGitpod()) {
123 | return;
124 | }
125 |
126 | await this.showPage();
127 | }
128 |
129 | private async getWebviewContentLocal(webview: Webview, htmlFile: Uri, cssUrl: Uri, logoUrl: Uri): Promise {
130 | const pageBuilder = await WhatsNewPageBuilder.newBuilder(webview, htmlFile);
131 | let html = pageBuilder.updateExtensionPublisher(this.publisher)
132 | .updateExtensionDisplayName(this.extension.packageJSON.displayName)
133 | .updateExtensionName(this.extensionName)
134 | .updateExtensionVersion(this.extension.packageJSON.version)
135 | .updateRepositoryUrl(this.extension.packageJSON.repository.url.slice(
136 | 0, this.extension.packageJSON.repository.url.length - 4))
137 | .updateRepositoryIssues(this.extension.packageJSON.bugs.url)
138 | .updateRepositoryHomepage(this.extension.packageJSON.homepage)
139 | .updateCSS(webview.asWebviewUri(cssUrl).toString())
140 | .updateHeader(this.contentProvider.provideHeader(webview.asWebviewUri(logoUrl).toString()))
141 | .updateChangeLog(this.contentProvider.provideChangeLog())
142 | .updateSponsors(this.sponsorProvider?.provideSponsors())
143 | .updateSupportChannels(this.contentProvider.provideSupportChannels())
144 | .updateSocialMedias(this.socialMediaProvider?.provideSocialMedias())
145 | .build();
146 |
147 | html = html
148 | .replace(/#{cspSource}/g, webview.cspSource)
149 | .replace(/#{root}/g, webview.asWebviewUri(this.context.extensionUri).toString());
150 |
151 | return html;
152 | }
153 | }
--------------------------------------------------------------------------------
/src/PageBuilder.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Alessandro Fragnani. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | // import * as fs from "fs";
7 | import * as semver from "semver";
8 | import { Uri, Webview, workspace } from "vscode";
9 | import { ChangeLogItem, ChangeLogIssue, ChangeLogVersion, ChangeLogKind, Header, Sponsor, IssueKind, SupportChannel, SocialMedia } from "./ContentProvider";
10 |
11 | export async function readRAWFileUri(uri: Uri): Promise {
12 | const bytes = await workspace.fs.readFile(uri);
13 | return new TextDecoder('utf-8').decode(bytes);
14 | }
15 |
16 | export class WhatsNewPageBuilder {
17 |
18 | public static async newBuilder(webview: Webview, htmlFile: Uri): Promise {
19 | const html = await readRAWFileUri(htmlFile);
20 | return new WhatsNewPageBuilder(webview, html);
21 | }
22 |
23 | private htmlFile: string;
24 | private repositoryUrl!: string;
25 | private webview: Webview;
26 |
27 | constructor(webview: Webview, htmlFile: string) {
28 | this.webview = webview;
29 | this.htmlFile = htmlFile;
30 | }
31 |
32 | public updateExtensionPublisher(publisher: string) {
33 | this.htmlFile = this.htmlFile.replace(/\$\{publisher\}/g, publisher);
34 | return this;
35 | }
36 |
37 | public updateExtensionDisplayName(extensionDisplayName: string) {
38 | this.htmlFile = this.htmlFile.replace(/\$\{extensionDisplayName\}/g, extensionDisplayName);
39 | return this;
40 | }
41 |
42 | public updateExtensionName(extensionName: string) {
43 | this.htmlFile = this.htmlFile.replace(/\$\{extensionName\}/g, extensionName);
44 | return this;
45 | }
46 |
47 | public updateExtensionVersion(extensionVersion: string) {
48 | this.htmlFile = this.htmlFile.replace("${extensionVersion}",
49 | `${semver.major(extensionVersion)}.${semver.minor(extensionVersion)}`);
50 | return this;
51 | }
52 |
53 | public updateRepositoryUrl(repositoryUrl: string) {
54 | this.htmlFile = this.htmlFile.replace(/\$\{repositoryUrl\}/g, repositoryUrl);
55 | this.repositoryUrl = repositoryUrl;
56 | return this;
57 | }
58 |
59 | public updateRepositoryIssues(repositoryIssues: string) {
60 | this.htmlFile = this.htmlFile.replace("${repositoryIssues}", repositoryIssues);
61 | return this;
62 | }
63 |
64 | public updateRepositoryHomepage(repositoryHomepage: string) {
65 | this.htmlFile = this.htmlFile.replace("${repositoryHomepage}", repositoryHomepage);
66 | return this;
67 | }
68 |
69 | public updateCSS(cssUrl: string): WhatsNewPageBuilder {
70 | this.htmlFile = this.htmlFile.replace("${cssUrl}", cssUrl);
71 | return this;
72 | }
73 |
74 | public updateHeader(header: Header): WhatsNewPageBuilder {
75 | this.htmlFile = this.htmlFile.replace("${headerLogo}", header.logo.src);
76 | this.htmlFile = this.htmlFile.replace("${headerWidth}", header.logo.width.toString());
77 | this.htmlFile = this.htmlFile.replace("${headerHeight}", header.logo.height.toString());
78 | this.htmlFile = this.htmlFile.replace("${headerMessage}", header.message);
79 | return this;
80 | }
81 |
82 | public updateChangeLog(changeLog: ChangeLogItem[]): WhatsNewPageBuilder {
83 | let changeLogString = "";
84 |
85 | for (const cl of changeLog) {
86 | if (cl.kind === ChangeLogKind.VERSION) {
87 | const cc: ChangeLogVersion = cl.detail;
88 | const borderTop = changeLogString === "" ? "" : "changelog__version__borders__top";
89 | changeLogString = changeLogString.concat(
90 | `
91 | ${cc.releaseNumber}
92 | ${cc.releaseDate}
93 | `);
94 | } else {
95 | const badge: string = this.getBadgeFromChangeLogKind(cl.kind);
96 | let message: string;
97 |
98 | if (typeof cl.detail === "string") {
99 | message = cl.detail
100 | } else {
101 | const cc: ChangeLogIssue = cl.detail;
102 | if (cc.kind === IssueKind.Issue) {
103 | message = `${cc.message}
104 | (Issue #${cc.id} )`
106 | } else {
107 | message = `${cc.message}
108 | (Thanks to ${cc.kudos} - PR #${cc.id} )`
110 | }
111 | }
112 |
113 | changeLogString = changeLogString.concat(
114 | `${cl.kind}
115 | ${message}
116 | `
117 | );
118 | }
119 | }
120 | this.htmlFile = this.htmlFile.replace("${changeLog}", changeLogString);
121 | return this;
122 | }
123 |
124 | public updateSponsors(sponsors: Sponsor[] | undefined): WhatsNewPageBuilder {
125 | if (!sponsors || sponsors.length === 0) {
126 | this.htmlFile = this.htmlFile.replace("${sponsors}", "");
127 | return this;
128 | }
129 |
130 | let sponsorsString = `
131 |
Sponsors `;
132 |
133 | for (const sp of sponsors) {
134 | if (sp.message) {
135 | sponsorsString = sponsorsString.concat(
136 | `
137 |
138 |
139 |
140 | ${sp.message}
141 | ${sp.extra} `
142 | );
143 | } else {
144 | sponsorsString = sponsorsString.concat(
145 | ` `
149 | );
150 | }
151 | }
152 | sponsorsString = sponsorsString.concat("");
153 | this.htmlFile = this.htmlFile.replace("${sponsors}", sponsorsString);
154 | return this;
155 | }
156 |
157 | public updateSupportChannels(supportChannels: SupportChannel[]): WhatsNewPageBuilder {
158 | if (supportChannels.length === 0) {
159 | this.htmlFile = this.htmlFile.replace("${supportChannels}", "");
160 | return this;
161 | }
162 |
163 | let supportChannelsString = `");
173 | this.htmlFile = this.htmlFile.replace("${supportChannels}", supportChannelsString);
174 | return this;
175 | }
176 |
177 | public updateSocialMedias(socialMedias: SocialMedia[] | undefined): WhatsNewPageBuilder {
178 | if (!socialMedias || socialMedias.length === 0) {
179 | this.htmlFile = this.htmlFile.replace("${socialMedias}", "");
180 | return this;
181 | }
182 |
183 | let socialMediasString = '';
184 |
185 | for (const sm of socialMedias) {
186 | socialMediasString = socialMediasString.concat(
187 | `${sm.title} `
188 | );
189 | }
190 | this.htmlFile = this.htmlFile.replace("${socialMedias}", socialMediasString);
191 | return this;
192 | }
193 |
194 | public build(): string {
195 | return this.htmlFile.toString();
196 | }
197 |
198 | private getBadgeFromChangeLogKind(kind: ChangeLogKind): string {
199 | switch (kind) {
200 | case ChangeLogKind.NEW:
201 | return "added";
202 |
203 | case ChangeLogKind.CHANGED:
204 | return "changed";
205 |
206 | case ChangeLogKind.FIXED:
207 | return "fixed";
208 |
209 | case ChangeLogKind.INTERNAL:
210 | return "internal";
211 |
212 | default:
213 | return "internal";
214 | }
215 | }
216 | }
--------------------------------------------------------------------------------
/ui/main.css:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Eric Amodio & Alessandro Fragnani. All rights reserved.
3 | * Licensed under the MIT License. See License.md in the project root for license information.
4 | *
5 | * Original Author: Eric Amodio @ GitLens
6 | * Modifications by: Alessandro Fragnani
7 | *--------------------------------------------------------------------------------------------*/
8 |
9 | html {
10 | height: 100%; }
11 |
12 | body {
13 | background-color: var(--vscode-editor-background);
14 | color: var(--vscode-editor-foreground);
15 | font-family: var(--vscode-font-family);
16 | height: 100%;
17 | opacity: 0.85;
18 | line-height: 1.4; }
19 |
20 | a {
21 | border: 0;
22 | color: var(--vscode-textLink-foreground);
23 | font-weight: 400;
24 | outline: none;
25 | text-decoration: none; }
26 | a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
27 | color: inherit;
28 | text-decoration: none; }
29 | .vscode-light a:focus {
30 | outline-color: var(--vscode-textLink-foreground); }
31 | .vscode-dark a:focus {
32 | outline-color: var(--vscode-textLink-foreground); }
33 |
34 | b {
35 | font-weight: 600; }
36 |
37 | h1 {
38 | border: none;
39 | font-size: 2.77em;
40 | font-weight: 400;
41 | margin: 0;
42 | padding: 0;
43 | white-space: nowrap; }
44 |
45 | h2 {
46 | font-size: 1.5em;
47 | font-weight: 200;
48 | line-height: normal;
49 | /* margin: 1em 0 0.3em 0; */
50 | white-space: nowrap; }
51 |
52 | h3 {
53 | font-size: 1.17em;
54 | font-weight: 200;
55 | line-height: normal;
56 | margin: 1em 0 0.3em 0;
57 | white-space: nowrap; }
58 |
59 | header {
60 | align-items: center;
61 | display: flex;
62 | flex-wrap: wrap;
63 | justify-content: center;
64 | margin-bottom: 1em; }
65 |
66 | input, select, button {
67 | font-family: var(--vscode-font-family);
68 | font-size: inherit;
69 | margin: 0; }
70 |
71 | input {
72 | background: none;
73 | border: none;
74 | cursor: pointer;
75 | margin: 0;
76 | padding: 0 10px; }
77 | input:focus {
78 | background: rgba(0, 0, 0, 0.1);
79 | border-radius: 5px;
80 | outline: none; }
81 | input[disabled] {
82 | color: var(--vscode-input-foreground);
83 | cursor: default; }
84 |
85 | label {
86 | cursor: pointer; }
87 |
88 | section {
89 | display: flex;
90 | flex-wrap: wrap;
91 | margin-bottom: 1em;
92 | /* padding: 1rem; */
93 | }
94 | .vscode-light section {
95 | background-color: var(--vscode-editor-background); }
96 | .vscode-dark section {
97 | background-color: var(--vscode-editor-background); }
98 |
99 | ul {
100 | font-size: 1em;
101 | list-style: none;
102 | margin: 0;
103 | padding: 0; }
104 |
105 | .button {
106 | background: none;
107 | border: none;
108 | border-radius: 3px;
109 | cursor: pointer;
110 | display: inline-block;
111 | font-size: 0.8em;
112 | letter-spacing: 0.25em;
113 | margin: 1em 0.5em;
114 | padding: 1em 1.75em;
115 | text-decoration: none;
116 | text-transform: uppercase;
117 | user-select: none;
118 | white-space: nowrap; }
119 | .button:focus {
120 | outline: none; }
121 |
122 | .button--big {
123 | font-size: 1.25em; }
124 |
125 | .button--flat {
126 | border: 1px solid rgba(255, 255, 255, 0.6);
127 | color: white !important;
128 | transition: background-color 250ms, border-color 250ms, color 250ms; }
129 | .button--flat:hover {
130 | background-color: white;
131 | color: black !important; }
132 | .preload .button--flat {
133 | transition-duration: 0s !important; }
134 |
135 | .button--flat-inverse {
136 | background-color: white;
137 | border: 1px solid white;
138 | color: black !important;
139 | font-weight: 600;
140 | transition: background-color 250ms, border-color 250ms, color 250ms; }
141 | .button--flat-inverse:hover {
142 | background: rgba(0, 0, 0, 0.2);
143 | border-color: rgba(255, 255, 255, 0.6);
144 | color: white !important; }
145 | .preload .button--flat-inverse {
146 | transition-duration: 0s !important; }
147 |
148 | .button--flat-primary {
149 | background-color: var(--vscode-button-background);
150 | border: 1px solid var(--vscode-button-background);
151 | color: white !important;
152 | font-weight: 600;
153 | transition: background-color 250ms, border-color 250ms, color 250ms; }
154 | .button--flat-primary:hover {
155 | background-color: white;
156 | border-color: white;
157 | color: black !important; }
158 | .preload .button--flat-primary {
159 | transition-duration: 0s !important; }
160 |
161 | .button-group {
162 | display: flex;
163 | flex-wrap: wrap; }
164 | .button-group .button {
165 | white-space: nowrap; }
166 |
167 | .button-group--support-alefragnani {
168 | justify-content: center;
169 | margin: 1.5em 0 1em 0; }
170 | .button-group--support-alefragnani .button {
171 | margin-left: 1.5em;
172 | margin-right: 1.5em;
173 | white-space: nowrap; }
174 |
175 | .button-group--support-alefragnani-sidebar {
176 | font-size: 11px; }
177 | .button-group--support-alefragnani-sidebar .button {
178 | font-weight: 400;
179 | margin: 0.75em 0; }
180 |
181 | .changelog {
182 | /* border-radius: 5em;*/
183 | margin: 0 2em 1em 0; }
184 | .vscode-light .changelog {
185 | background-color: var(--vscode-editor-background);
186 | /* box-shadow: inset 0 0 6px 0px rgba(0, 0, 0, 0.05); */
187 | }
188 | .vscode-dark .changelog {
189 | background-color: var(--vscode-editor-background);
190 | /* box-shadow: inset 0 0 6px 0px rgba(255, 255, 255, 0.075); */
191 | }
192 |
193 | .changelog__badge {
194 | background-color: var(--vscode-activityBarBadge-background);
195 | border-radius: 3px;
196 | box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.5);
197 | color: #e6e6e6;
198 | display: inline-block;
199 | font-size: 11px;
200 | font-weight: 600;
201 | margin-right: 1.25em;
202 | padding: 2px 5px;
203 | text-align: center;
204 | text-transform: uppercase;
205 | vertical-align: text-top;
206 | width: 65px; }
207 |
208 | .changelog__badge--added {
209 | background-color: var(--vscode-problemsInfoIcon-foreground); }
210 |
211 | .changelog__badge--changed {
212 | background-color: var(--vscode-problemsWarningIcon-foreground); }
213 |
214 | .changelog__badge--fixed {
215 | background-color: var(--vscode-problemsErrorIcon-foreground); }
216 |
217 | .changelog__badge--internal {
218 | background-color: var(--vscode-problemsWarningIcon-foreground); }
219 |
220 | .changelog__badge--version {
221 | background-color: var(--vscode-activityBarBadge-background);
222 | height: 24px;
223 | vertical-align: middle;
224 | font-size: 15px; }
225 |
226 | .uppercase {
227 | text-transform: uppercase;
228 | }
229 |
230 | .changelog__version__borders {
231 | border-color: rgba(125, 125, 125, .15);
232 | border-spacing: 50px;
233 | border-width: thin;
234 | padding-top: 10px;
235 | padding-bottom: 10px;
236 | }
237 |
238 | .changelog__version__borders__top {
239 | border-top-style: solid;
240 | }
241 |
242 | .changelog__date {
243 | font-size: 0.8em;
244 | font-weight: 600;
245 | font-variant: small-caps;
246 | opacity: 0.9;
247 | margin-left: -7px;
248 | vertical-align: middle; }
249 |
250 | .changelog__details {
251 | align-items: center;
252 | display: flex;
253 | justify-content: center;
254 | position: relative; }
255 | .changelog__details:before {
256 | border-left: 1px solid rgba(122, 122, 122, 0.15);
257 | content: " ";
258 | height: calc(100% + 7px);
259 | left: 37px;
260 | position: absolute;
261 | top: 1px; }
262 |
263 | .changelog__image {
264 | margin: 1em 0;
265 | max-width: 65%; }
266 |
267 | .changelog__list {
268 | flex: 100% 0 1;
269 | font-size: 1.2em;
270 | font-weight: 200;
271 | line-height: 1.7;
272 | list-style-type: none;
273 | margin: 1em; }
274 | .changelog__list li {
275 | margin-bottom: 0.5em; }
276 |
277 | .changelog__list-item--version {
278 | margin: 2em 0 0.5em 0; }
279 |
280 | .changelog__scroller {
281 | flex: 100% 0 0;
282 | /* margin: 1.5em 0 0 0; */
283 | max-height: 400px;
284 | overflow-y: auto; }
285 | /* .vscode-dark .changelog__scroller {
286 | border-bottom: 1px solid rgba(255, 255, 255, 0.04); }
287 | .vscode-light .changelog__scroller {
288 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); } */
289 |
290 | .changelog__title {
291 | flex: 100% 0 0;
292 | text-align: center; }
293 |
294 | .changelog__version {
295 | background-color: yellow; /* var(--vscode-activityBarBadge-background);*/
296 | /* border-bottom: 2px solid #0bb892; */
297 | border-radius: 3px;
298 | /* color: var(--vscode-activityBarBadge-foreground); */
299 | margin: 0 0.25em;
300 | padding: 2px 10px;
301 | text-align: center;
302 |
303 | color: black;
304 | border-color: blue;
305 | border-width: 2px;
306 | font-weight: 600;
307 |
308 | vertical-align: bottom; }
309 |
310 | .command {
311 | font-weight: 600;
312 | padding: 1px 3px; }
313 |
314 | .container {
315 | align-items: center;
316 | display: flex;
317 | justify-content: center;
318 | min-height: 100%;
319 | min-width: 100%; }
320 |
321 | .content {
322 | margin-top: 1em;
323 | max-width: 1200px;
324 | min-width: 450px;
325 | width: 90%; }
326 |
327 | .cta__container {
328 | display: flex;
329 | flex-wrap: wrap;
330 | justify-content: center;
331 | /*margin-bottom: 3em;*/ }
332 | .cta__container p {
333 | margin-top: -1em;
334 | opacity: 0.4; }
335 |
336 | .cta__secondary {
337 | margin: 0 1em; }
338 |
339 | .section-list {
340 | font-weight: 200;
341 | list-style: disc;
342 | margin: 0 0 2em 2em; }
343 | .section-list li {
344 | margin-bottom: 0.5em; }
345 |
346 | .header__blurb {
347 | color: var(--vscode-editor-foreground);
348 | /* flex: 55% 2 1; */
349 | font-size: 1.2em;
350 | font-weight: 200;
351 | margin: 1em 0 1em 1em;
352 | min-width: 345px; }
353 | @media all and (max-width: 880px) {
354 | .header__blurb {
355 | margin: 0 1em; } }
356 |
357 | .header__link {
358 | color: var(--vscode-input-foreground);
359 | outline: none;
360 | text-align: center; }
361 | .header__link:hover, .header__link:active, .header__link:focus {
362 | color: var(--vscode-input-foreground);
363 | outline: none; }
364 |
365 | .header__logo {
366 | display: flex;
367 | flex-wrap: nowrap; }
368 |
369 | .header__subtitle {
370 | color: var(--vscode-input-foreground);
371 | display: block;
372 | font-size: 2em;
373 | font-weight: 100;
374 | margin-top: -0.2em;
375 | white-space: nowrap; }
376 |
377 | .icon {
378 | background-color: var(--vscode-input-background);
379 | display: inline-block;
380 | height: 24px;
381 | margin-bottom: -9px;
382 | width: 19px; }
383 |
384 | .icon__info {
385 | -webkit-mask-image: url('data:image/svg+xml;utf8, ');
386 | -webkit-mask-repeat: no-repeat;
387 | mask-image: url('data:image/svg+xml;utf8, ');
388 | mask-repeat: no-repeat; }
389 |
390 | .icon--lg {
391 | height: 36px;
392 | margin-bottom: -16px;
393 | width: 30px; }
394 |
395 | .image__logo {
396 | margin: 9px 1em 0 0;
397 | max-height: 64px;
398 | max-width: 64px; }
399 |
400 | .image__preview {
401 | border-radius: 8px;
402 | box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.8), 0px 0px 12px 1px rgba(0, 0, 0, 0.5); }
403 |
404 | .image__preview--overlay {
405 | left: 0;
406 | position: absolute;
407 | top: 0; }
408 |
409 | .list-button {
410 | border: none;
411 | color: var(--vscode-button-foreground);
412 | cursor: pointer;
413 | font-size: 1em;
414 | height: 5em;
415 | margin: 5px 1px;
416 | max-width: 475px;
417 | padding: 12px 10px;
418 | text-align: left;
419 | width: calc(100% - 2px); }
420 | .vscode-light .list-button {
421 | background-color: var(--vscode-editor-background); }
422 | .vscode-dark .list-button {
423 | background-color: var(--vscode-editor-background); }
424 | .vscode-light .list-button:hover, .vscode-light .list-button:focus {
425 | background-color: var(--vscode-editor-background); }
426 | .vscode-dark .list-button:hover, .vscode-dark .list-button:focus {
427 | background-color: var(--vscode-editor-background); }
428 | .list-button:focus {
429 | outline: 0; }
430 | .vscode-light .list-button:focus {
431 | border: 1px solid var(--vscode-editor-background); }
432 | .vscode-dark .list-button:focus {
433 | border: 1px solid var(--vscode-editor-background); }
434 |
435 | .list-button__title {
436 | font-size: 1em;
437 | font-weight: 400;
438 | margin: 0;
439 | margin-bottom: .25em;
440 | overflow: hidden;
441 | text-overflow: ellipsis; }
442 |
443 | .list-button__detail {
444 | color: var(--vscode-button-foreground);
445 | display: inline-block;
446 | overflow: hidden;
447 | text-overflow: ellipsis;
448 | white-space: nowrap;
449 | width: 100%; }
450 |
451 | .page-header {
452 | margin: 1em 0 2em 0;
453 | position: relative; }
454 |
455 | .page-header__title {
456 | font-size: 3em;
457 | margin: 0; }
458 |
459 | .page-header__subtitle {
460 | color: var(--vscode-input-foreground);
461 | margin: 0; }
462 |
463 | .section__preview {
464 | flex: 30% 1 1;
465 | margin-left: 2em;
466 | min-width: 400px;
467 | position: relative; }
468 |
469 | .section__settings {
470 | display: flex;
471 | flex: 100% 1 0;
472 | flex-wrap: wrap;
473 | margin-right: 1em; }
474 |
475 | .section__header {
476 | align-items: baseline;
477 | cursor: pointer;
478 | display: flex;
479 | flex: 100% 0 0;
480 | flex-wrap: wrap;
481 | margin-bottom: 2em;
482 | position: relative; }
483 | .section__header:after {
484 | background-color: var(--vscode-editor-background);
485 | content: '';
486 | height: 40px;
487 | -webkit-mask-image: url('data:image/svg+xml;utf8, ');
488 | -webkit-mask-repeat: no-repeat;
489 | -webkit-mask-size: 32px 40px;
490 | mask-image: url('data:image/svg+xml;utf8, ');
491 | mask-repeat: no-repeat;
492 | mask-size: 32px 40px;
493 | position: absolute;
494 | right: 0;
495 | top: 0;
496 | transition: transform 500ms cubic-bezier(0, 1.5, 1, 1);
497 | width: 32px; }
498 | .section__header.collapsed {
499 | margin-bottom: 0; }
500 | .section__header.collapsed:after {
501 | transform: rotateX(180deg); }
502 | .section__header.collapsed + .section__settings {
503 | display: none; }
504 |
505 | .section__title {
506 | flex: 100% 1 0;
507 | margin: 0; }
508 |
509 | .section-group__content {
510 | flex: auto 1 1;
511 | min-width: 0;
512 | max-width: 60%; }
513 |
514 | .section-group__sidebar {
515 | align-self: flex-start;
516 | margin: 0 0 0 3em;
517 | position: sticky;
518 | top: 0; }
519 | .section-group__sidebar li {
520 | white-space: nowrap; }
521 |
522 | @media all and (max-width: 860px) {
523 | .section-group__sidebar--settings {
524 | display: none; } }
525 |
526 | .section-group__sidebar--welcome {
527 | margin-right: 3em; }
528 | @media all and (max-width: 560px) {
529 | .section-group__sidebar--welcome {
530 | display: none; } }
531 |
532 | .section-group-section p {
533 | font-size: 1.1em;
534 | font-weight: 200;
535 | margin: 1em 0.25em; }
536 |
537 | .section-group-section__cols {
538 | display: flex;
539 | flex-wrap: wrap; }
540 |
541 | .section-group-section__col {
542 | flex: 45% 1 1;
543 | margin: 0 4px;
544 | min-width: 300px; }
545 |
546 | .section-groups {
547 | display: flex;
548 | justify-content: space-around;
549 | margin-bottom: 50%;
550 | margin-top: -1em; }
551 |
552 | .settings-group {
553 | flex: 2 1;
554 | margin-bottom: 1em;
555 | min-width: 300px; }
556 |
557 | .settings-group__hint--more {
558 | display: block;
559 | margin-left: 34px;
560 | margin-top: 0.5em; }
561 |
562 | .settings-group__setting {
563 | align-items: baseline;
564 | display: flex;
565 | flex: 100% 1 1;
566 | flex-wrap: wrap;
567 | margin-bottom: 0.75em; }
568 | .settings-group__setting input[type="checkbox"] {
569 | flex: 16px 0 0;
570 | height: 16px;
571 | margin: 0 0.75em 0 0;
572 | position: relative;
573 | top: 3px;
574 | width: 16px; }
575 | .settings-group__setting label {
576 | flex: auto 0 1;
577 | margin-bottom: 0.75em; }
578 | .settings-group__setting[disabled] label {
579 | color: var(--vscode-input-placeholderForeground);
580 | cursor: default; }
581 |
582 | .setting__hint--indent-2 {
583 | margin-left: 5em; }
584 |
585 | .setting__hint--indent-4 {
586 | margin-left: 7em; }
587 |
588 | .sidebar-group {
589 | margin-top: 0; }
590 |
591 | .bold {
592 | font-weight: 600; }
593 |
594 | .center {
595 | text-align: center; }
596 |
597 | .hidden {
598 | display: none !important; }
599 |
600 | .mb-2 {
601 | margin-bottom: 2em !important; }
602 |
603 | .ml-1 {
604 | margin-left: 1em; }
605 |
606 | .ml-2 {
607 | margin-left: 2em; }
608 |
609 | .ml-4 {
610 | margin-left: 4em; }
611 |
612 | .non-interactive {
613 | cursor: default !important; }
614 |
615 | .nowrap {
616 | flex-wrap: nowrap !important; }
617 |
618 | .vscode-dark .light {
619 | display: none;
620 | }
621 | .vscode-light .dark {
622 | display: none;
623 | }
624 |
--------------------------------------------------------------------------------