1
Fork 0

Add the Markdown Snippets storage code.

This commit is contained in:
Bauke 2023-11-19 13:00:22 +01:00
parent 459352f173
commit 4fe4783262
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
3 changed files with 218 additions and 0 deletions

View File

@ -35,6 +35,7 @@ export enum Data {
EnabledFeatures = "enabled-features",
KnownGroups = "known-groups",
LatestActiveFeatureTab = "latest-active-feature-tab",
MarkdownSnippet = "markdown-snippet",
MiscellaneousEnabledFeatures = "miscellaneous-enabled-features",
OnSiteNewLabel = "on-site-new-label",
RandomizeUsernameColors = "randomize-username-colors",

View File

@ -13,6 +13,7 @@ import {collectUserLabels} from "./user-label.js";
export * from "./anonymize-usernames.js";
export * from "./enums.js";
export * from "./hide-topics.js";
export * from "./markdown-toolbar.js";
export * from "./username-color.js";
export * from "./user-label.js";

View File

@ -0,0 +1,216 @@
import browser from "webextension-polyfill";
import {createValue, type Value} from "@holllo/webextension-storage";
import {Data} from "./enums.js";
/**
* Definition of a Markdown snippet to be used with the Markdown Toolbar feature.
*/
export type MarkdownSnippet = {
/** Whether the snippet is enabled and should show in the toolbar. */
enabled: boolean;
/** A unique identifier for the snippet. */
id: number;
/** Whether the snippet should be placed in the "More..." dropdown. */
inDropdown: boolean;
/** The Markdown content of the snippet. */
markdown: string;
/** The name of the snippet to be shown in the toolbar. */
name: string;
/** The position */
position: number;
};
/**
* Markers used to specify where the cursor should be placed after the snippet
* is inserted.
*/
export enum MarkdownSnippetMarker {
/**
* The marker for the default position the cursor should be placed at.
*/
Cursor = "<cursor>",
/**
* If text was selected before the snippet was inserted, the cursor will first
* attempt to be placed at the `SelectedCursor` position, if there is no
* `SelectedCursor` in the snippet then `Cursor` is used instead. The selected
* text will be inserted at the `Cursor` position.
*/
SelectedCursor = "<selected-cursor>",
}
/* eslint-disable-next-line @typescript-eslint/naming-convention */
const {Cursor, SelectedCursor} = MarkdownSnippetMarker;
export const builtinSnippets: MarkdownSnippet[] = [
{
enabled: true,
inDropdown: false,
markdown: `[${Cursor}](${SelectedCursor})`,
name: "Link",
},
{
enabled: true,
inDropdown: false,
markdown: `\`\`\`${SelectedCursor}\n${Cursor}\n\`\`\``,
name: "Code",
},
{
enabled: true,
inDropdown: false,
markdown: `~~${Cursor}~~`,
name: "Strikethrough",
},
{
enabled: true,
inDropdown: false,
markdown: `<details>\n<summary>Click to expand spoiler.</summary>\n\n${Cursor}\n</details>`,
name: "Spoilerbox",
},
{
enabled: true,
inDropdown: true,
markdown: `**${Cursor}**`,
name: "Bold",
},
{
enabled: true,
inDropdown: true,
markdown: `\n\n---\n\n${Cursor}`,
name: "Horizontal Divider",
},
{
enabled: true,
inDropdown: true,
markdown: `\`${Cursor}\``,
name: "Inline Code",
},
{
enabled: true,
inDropdown: true,
markdown: `*${Cursor}*`,
name: "Italic",
},
{
enabled: true,
inDropdown: true,
markdown: `1. ${Cursor}`,
name: "Ordered List",
},
{
enabled: true,
inDropdown: true,
markdown: `<small>${Cursor}</small>`,
name: "Small",
},
{
enabled: true,
inDropdown: true,
markdown: `* ${Cursor}`,
name: "Unordered List",
},
].map((snippet, index) => {
return {
...snippet,
// Make the builtin snippets use a negative ID so there will never be a
// conflict with user-created snippets if for some reason they need to be
// reinserted into storage.
id: index - 100,
position: index + 1,
} satisfies MarkdownSnippet;
});
/**
* Create a {@link Value}-wrapped {@link MarkdownSnippet}.
*/
export async function createValueMarkdownSnippet(
snippet: MarkdownSnippet,
): Promise<Value<MarkdownSnippet>> {
return createValue<MarkdownSnippet>({
deserialize: (input) => JSON.parse(input) as MarkdownSnippet,
serialize: (input) => JSON.stringify(input),
key: `${Data.MarkdownSnippet}-${snippet.id}`,
value: snippet,
storage: browser.storage.sync,
});
}
/**
* Get all Markdown snippets from storage and combine them into a single array.
*/
export async function collectMarkdownSnippets(): Promise<
Array<Value<MarkdownSnippet>>
> {
const storage = await browser.storage.sync.get();
const snippets: Array<Value<MarkdownSnippet>> = [];
for (const [key, value] of Object.entries(storage)) {
if (!key.startsWith(Data.MarkdownSnippet)) {
continue;
}
snippets.push(
await createValueMarkdownSnippet(
JSON.parse(value as string) as MarkdownSnippet,
),
);
}
if (snippets.length === 0) {
// If no snippets are in storage, grab all the builtin ones and save them.
snippets.push(
...(await Promise.all(
builtinSnippets.map(async (snippet) =>
createValueMarkdownSnippet(snippet),
),
)),
);
for (const snippet of snippets) {
await snippet.save();
}
}
return sortSnippets(snippets);
}
/**
* Create a new user label ID by getting the current highest existing ID in
* storage and adding 1 to it. Defaults to 1 when no there are no existing
* labels.
*/
export async function newMarkdownSnippetId(): Promise<number> {
const snippets = await collectMarkdownSnippets();
let newId = 1;
if (snippets.length > 0) {
newId = snippets.sort((a, b) => b.value.id - a.value.id)[0].value.id + 1;
}
// Builtin snippets will have a negative ID so reset back to 1 if only builtin
// snippets are in storage.
if (newId < 0) {
newId = 1;
}
return newId;
}
/**
* Sort the snippets by their position first and their name second.
*/
export function sortSnippets(
snippets: Array<Value<MarkdownSnippet>>,
): Array<Value<MarkdownSnippet>> {
return snippets.sort((a, b) => {
const position = a.value.position - b.value.position;
if (position === 0) {
return a.value.name.localeCompare(b.value.name);
}
return position;
});
}