Add the Markdown Toolbar Editor.
This commit is contained in:
parent
e752853a58
commit
75cd843d27
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Tildes ReExtended</title>
|
||||||
|
<link rel="shortcut icon" href="/tildes-reextended.png"
|
||||||
|
type="image/png">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
This web extension does not work without JavaScript, sorry. :(
|
||||||
|
</noscript>
|
||||||
|
<script type="module" src="/options/markdown-toolbar-editor.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -78,6 +78,7 @@ const options: esbuild.BuildOptions = {
|
||||||
},
|
},
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
path.join(sourceDir, "background/setup.ts"),
|
path.join(sourceDir, "background/setup.ts"),
|
||||||
|
path.join(sourceDir, "options/markdown-toolbar-editor.tsx"),
|
||||||
path.join(sourceDir, "options/setup.tsx"),
|
path.join(sourceDir, "options/setup.tsx"),
|
||||||
path.join(sourceDir, "options/user-label-editor.tsx"),
|
path.join(sourceDir, "options/user-label-editor.tsx"),
|
||||||
path.join(sourceDir, "content-scripts/setup.tsx"),
|
path.join(sourceDir, "content-scripts/setup.tsx"),
|
||||||
|
|
|
@ -15,11 +15,12 @@ export function MarkdownToolbarSetting(props: SettingProps): JSX.Element {
|
||||||
/>
|
/>
|
||||||
/spoilerbox syntax. If you have text selected, the Markdown will be
|
/spoilerbox syntax. If you have text selected, the Markdown will be
|
||||||
inserted around your text.
|
inserted around your text.
|
||||||
<br />A full list of the snippets is available{" "}
|
<br />
|
||||||
<Link
|
You can edit the available snippets and their position in the toolbar
|
||||||
url="https://gitlab.com/tildes-community/tildes-reextended/-/issues/12"
|
using the{" "}
|
||||||
text="on GitLab"
|
<a href="/options/markdown-toolbar-editor.html">
|
||||||
/>
|
Markdown Toolbar Editor
|
||||||
|
</a>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
|
|
@ -0,0 +1,465 @@
|
||||||
|
import {Component, type JSX, render, type RefObject, createRef} from "preact";
|
||||||
|
import {type Value} from "@holllo/webextension-storage";
|
||||||
|
import {initializeGlobals, log, Link} from "../utilities/exports.js";
|
||||||
|
import {
|
||||||
|
type MarkdownSnippet,
|
||||||
|
collectMarkdownSnippets,
|
||||||
|
createValueMarkdownSnippet,
|
||||||
|
newMarkdownSnippetId,
|
||||||
|
} from "../storage/exports.js";
|
||||||
|
import {runMarkdownToolbarFeature} from "../content-scripts/features/markdown-toolbar.js";
|
||||||
|
import "../scss/index.scss";
|
||||||
|
import "../scss/markdown-toolbar-editor.scss";
|
||||||
|
|
||||||
|
window.addEventListener("load", async () => {
|
||||||
|
initializeGlobals();
|
||||||
|
render(<App />, document.body);
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = Record<string, unknown>;
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
/**
|
||||||
|
* Snippets are stored as an array of tuples with the {@linkcode Value}-wrapped
|
||||||
|
* {@linkcode MarkdownSnippet} as the first item and a {@linkcode RefObject} to
|
||||||
|
* the {@linkcode SnippetEditor} component. This is done so we can have the
|
||||||
|
* "Save All" button in the main component but have the logic for saving in
|
||||||
|
* the editor components.
|
||||||
|
*/
|
||||||
|
snippets: Array<[Value<MarkdownSnippet>, RefObject<SnippetEditor>]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class App extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
snippets: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const snippets = await collectMarkdownSnippets();
|
||||||
|
this.setState({
|
||||||
|
snippets: snippets.map((snippet) => [snippet, createRef()]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
// Each time the main component updates we want to re-run the toolbar setup
|
||||||
|
// so the snippets are all updated and in their correct places.
|
||||||
|
runMarkdownToolbarFeature(
|
||||||
|
this.state.snippets
|
||||||
|
.map(([snippet, _ref]) => snippet)
|
||||||
|
.filter((snippet) => snippet.value.enabled),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addSnippet = async () => {
|
||||||
|
const id = await newMarkdownSnippetId();
|
||||||
|
const snippet = await createValueMarkdownSnippet({
|
||||||
|
enabled: true,
|
||||||
|
id,
|
||||||
|
inDropdown: false,
|
||||||
|
markdown: "",
|
||||||
|
name: `Snippet ${id}`,
|
||||||
|
position: 1,
|
||||||
|
});
|
||||||
|
await snippet.save();
|
||||||
|
const {snippets} = this.state;
|
||||||
|
snippets.push([snippet, createRef()]);
|
||||||
|
this.setState({snippets});
|
||||||
|
};
|
||||||
|
|
||||||
|
applyAndReload = async () => {
|
||||||
|
for (const [snippet, ref] of this.state.snippets) {
|
||||||
|
if (ref.current === null) {
|
||||||
|
throw new Error(
|
||||||
|
"SnippetEditor reference is null, this should be unreachable!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = ref.current;
|
||||||
|
if (editor.state.toBeRemoved) {
|
||||||
|
await snippet.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.state.hasUnsavedChanges) {
|
||||||
|
await ref.current.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.componentDidMount();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {snippets} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header class="page-header">
|
||||||
|
<h1>
|
||||||
|
<img src="/tildes-reextended.png" />
|
||||||
|
Markdown Toolbar Editor
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="page-main markdown-toolbar-editor">
|
||||||
|
<h2>Toolbar Preview</h2>
|
||||||
|
<p class="info">
|
||||||
|
The Toolbar Preview lets you test out your snippets here directly
|
||||||
|
without having to go to Tildes, with the only difference being that
|
||||||
|
rendering the Markdown isn't possible here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* The key attribute makes it so the mock re-renders on every update. */}
|
||||||
|
<MockMarkdownTextarea key={`mock-${Date.now()}`} />
|
||||||
|
|
||||||
|
<div class="snippets-title">
|
||||||
|
<h2>Snippets</h2>
|
||||||
|
<button class="add-new-snippet" onClick={this.addSnippet}>
|
||||||
|
New Snippet
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="apply-and-reload-snippets"
|
||||||
|
onClick={this.applyAndReload}
|
||||||
|
>
|
||||||
|
Apply & Reload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="snippet-usage-guide">
|
||||||
|
<summary>Usage Guide</summary>
|
||||||
|
|
||||||
|
<div class="inner">
|
||||||
|
<p>
|
||||||
|
Here you can create your own snippets and customize your
|
||||||
|
toolbar, each snippet has a number of configurable values:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Position</b>, the number next to the snippet name
|
||||||
|
determines in what order they will be placed in the toolbar.
|
||||||
|
Snippets with the same position will be sorted alphabetically.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Name</b>, the name of the snippet to display in the
|
||||||
|
toolbar.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Enable</b>, whether the snippet should be added to the
|
||||||
|
toolbar.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Display in the "More..." dropdown</b>, with this enabled
|
||||||
|
the snippet will be placed in the "More..." dropdown following
|
||||||
|
the same sorting rules as normal.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Snippet (Markdown)</b>, the snippet text itself in
|
||||||
|
Markdown.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There are also a few markers that will do special things when
|
||||||
|
used in a snippet:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
The <code><cursor></code> marker indicates where the
|
||||||
|
cursor should be positioned after inserting the snippet. If
|
||||||
|
this marker isn't used the cursor will be placed at the end of
|
||||||
|
the snippet.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The <code><selected-cursor></code> marker is used for
|
||||||
|
when you have text selected, placing the cursor at this
|
||||||
|
location and inserting the selected text in the snippet at the{" "}
|
||||||
|
<code><cursor></code> position. There is currently{" "}
|
||||||
|
<Link
|
||||||
|
text="a known bug"
|
||||||
|
url="https://gitlab.com/tildes-community/tildes-reextended/-/issues/47"
|
||||||
|
/>{" "}
|
||||||
|
when this marker is placed before the{" "}
|
||||||
|
<code><cursor></code> marker, causing the cursor
|
||||||
|
position to be incorrectly placed after inserting the snippet.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To reload the toolbar after you've made changes click the Apply
|
||||||
|
& Reload button. This will save all the snippets and recreate
|
||||||
|
the toolbar with your changes. Any snippets with unsaved changes
|
||||||
|
will have a yellow border and snippets that are going to be
|
||||||
|
removed will have a red border.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To remove a snippet click the Remove button, this will remove it
|
||||||
|
from storage but keep it loaded in the page. You can then click
|
||||||
|
the Apply & Reload button to permanently remove it or click the
|
||||||
|
Save or Undo buttons to get it back into storage.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
{snippets.map(([snippet, ref]) => (
|
||||||
|
<SnippetEditor key={snippet.value.id} ref={ref} snippet={snippet} />
|
||||||
|
))}
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnippetEditorProps = {
|
||||||
|
snippet: Value<MarkdownSnippet>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SnippetEditorState = {
|
||||||
|
enabled: MarkdownSnippet["enabled"];
|
||||||
|
hasUnsavedChanges: boolean;
|
||||||
|
inDropdown: MarkdownSnippet["inDropdown"];
|
||||||
|
markdown: MarkdownSnippet["markdown"];
|
||||||
|
name: MarkdownSnippet["name"];
|
||||||
|
position: MarkdownSnippet["position"];
|
||||||
|
toBeRemoved: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SnippetEditor extends Component<SnippetEditorProps, SnippetEditorState> {
|
||||||
|
constructor(props: SnippetEditorProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const {enabled, inDropdown, markdown, name, position} = props.snippet.value;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
enabled,
|
||||||
|
hasUnsavedChanges: false,
|
||||||
|
inDropdown,
|
||||||
|
markdown,
|
||||||
|
name,
|
||||||
|
position,
|
||||||
|
toBeRemoved: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
edit = <K extends keyof SnippetEditorState>(
|
||||||
|
key: K,
|
||||||
|
input: HTMLInputElement | HTMLTextAreaElement,
|
||||||
|
) => {
|
||||||
|
const unsavedChanges: Partial<SnippetEditorState> = {
|
||||||
|
hasUnsavedChanges: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (key === "position") {
|
||||||
|
this.setState({
|
||||||
|
...unsavedChanges,
|
||||||
|
[key]: Number(input.value),
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
["enabled", "inDropdown"].includes(key) &&
|
||||||
|
input instanceof HTMLInputElement
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
...unsavedChanges,
|
||||||
|
[key]: input.checked,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
...unsavedChanges,
|
||||||
|
[key]: input.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
save = async () => {
|
||||||
|
let {snippet} = this.props;
|
||||||
|
const {enabled, inDropdown, markdown, name, position, toBeRemoved} =
|
||||||
|
this.state;
|
||||||
|
|
||||||
|
snippet.value.enabled = enabled;
|
||||||
|
snippet.value.inDropdown = inDropdown;
|
||||||
|
snippet.value.markdown = markdown;
|
||||||
|
snippet.value.name = name;
|
||||||
|
snippet.value.position = position;
|
||||||
|
|
||||||
|
const isBuiltin = snippet.value.id < 0;
|
||||||
|
if (isBuiltin || toBeRemoved) {
|
||||||
|
// If the snippet is a builtin one, then remove it from storage and assign
|
||||||
|
// it a new ID indicating it was edited.
|
||||||
|
// If it was marked for removal then we also need to assign a new ID
|
||||||
|
// because it's possible a new snippet was assigned the old ID while this
|
||||||
|
// one was removed.
|
||||||
|
const id = await newMarkdownSnippetId();
|
||||||
|
if (isBuiltin) {
|
||||||
|
await snippet.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
snippet = await createValueMarkdownSnippet({
|
||||||
|
...snippet.value,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.snippet = snippet;
|
||||||
|
await this.props.snippet.save();
|
||||||
|
this.setState({hasUnsavedChanges: false, toBeRemoved: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
remove = async () => {
|
||||||
|
const toBeRemoved = !this.state.toBeRemoved;
|
||||||
|
if (toBeRemoved) {
|
||||||
|
await this.props.snippet.remove();
|
||||||
|
this.setState({toBeRemoved});
|
||||||
|
} else {
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
enabled,
|
||||||
|
hasUnsavedChanges,
|
||||||
|
inDropdown,
|
||||||
|
markdown,
|
||||||
|
name,
|
||||||
|
position,
|
||||||
|
toBeRemoved,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const onEdit = <K extends keyof SnippetEditorState>(
|
||||||
|
event: Event,
|
||||||
|
key: K,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
event.target instanceof HTMLInputElement ||
|
||||||
|
event.target instanceof HTMLTextAreaElement
|
||||||
|
) {
|
||||||
|
this.edit(key, event.target);
|
||||||
|
} else {
|
||||||
|
log("Tried to edit field with unknown event target type", true);
|
||||||
|
log(event, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editorClasses = [
|
||||||
|
"snippet-editor",
|
||||||
|
hasUnsavedChanges ? "unsaved-changes" : "",
|
||||||
|
toBeRemoved ? "to-be-removed" : "",
|
||||||
|
].join(" ");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={editorClasses}>
|
||||||
|
<div class="top-controls">
|
||||||
|
<input
|
||||||
|
class="snippet-position"
|
||||||
|
type="number"
|
||||||
|
placeholder="Snippet Position"
|
||||||
|
title="Snippet Position"
|
||||||
|
value={position}
|
||||||
|
onInput={(event) => {
|
||||||
|
onEdit(event, "position");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="snippet-name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Snippet Name"
|
||||||
|
title="Snippet Name"
|
||||||
|
value={name}
|
||||||
|
onInput={(event) => {
|
||||||
|
onEdit(event, "name");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label class="snippet-enabled">
|
||||||
|
Enable{" "}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={enabled}
|
||||||
|
onClick={(event) => {
|
||||||
|
onEdit(event, "enabled");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="snippet-in-dropdown">
|
||||||
|
Display in the "More..." dropdown{" "}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={inDropdown}
|
||||||
|
onClick={(event) => {
|
||||||
|
onEdit(event, "inDropdown");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
class="snippet-markdown"
|
||||||
|
placeholder="Snippet (Markdown)"
|
||||||
|
title="Snippet (Markdown)"
|
||||||
|
onInput={(event) => {
|
||||||
|
onEdit(event, "markdown");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{markdown}
|
||||||
|
</textarea>
|
||||||
|
|
||||||
|
<button class="snippet-save" onClick={this.save}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class={`snippet-remove destructive ${
|
||||||
|
toBeRemoved ? "to-be-removed" : ""
|
||||||
|
}`}
|
||||||
|
onClick={this.remove}
|
||||||
|
>
|
||||||
|
{toBeRemoved ? "Undo" : "Remove"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mocked version of the Markdown `<textarea>` for topics and comments.
|
||||||
|
* The HTML is a stripped down version of the `markdown_textarea` Jinja macro
|
||||||
|
* from the Tildes source (link below). If you end up changing this make sure
|
||||||
|
* that the Markdown Toolbar content script code is adapted too, since that's
|
||||||
|
* what is used for both attaching the toolbar here and on Tildes itself.
|
||||||
|
* https://gitlab.com/tildes/tildes/-/blob/d0d6b6d3dc8e31c94cb3c0cab7aecdd835b3836b/tildes/tildes/templates/macros/forms.jinja2#L4-33
|
||||||
|
*/
|
||||||
|
function MockMarkdownTextarea(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div class="form-markdown">
|
||||||
|
<header>
|
||||||
|
<menu class="tab tab-markdown-mode">
|
||||||
|
<li class="tab-item">
|
||||||
|
<button class="btn active">Edit</button>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item">
|
||||||
|
<button class="btn" disabled>
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</menu>
|
||||||
|
<Link
|
||||||
|
text="Formatting help"
|
||||||
|
url="https://docs.tildes.net/instructions/text-formatting"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
class="form-input"
|
||||||
|
name="markdown"
|
||||||
|
placeholder="Text (Markdown)"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ details {
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-wrapper,
|
.main-wrapper,
|
||||||
|
.markdown-toolbar-editor,
|
||||||
.page-header,
|
.page-header,
|
||||||
.page-footer,
|
.page-footer,
|
||||||
.user-label-editor {
|
.user-label-editor {
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
@use "button";
|
||||||
|
|
||||||
|
.markdown-toolbar-editor {
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
border: 1px solid var(--blue);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-markdown {
|
||||||
|
border: 1px solid var(--blue);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
border: 1px solid var(--foreground);
|
||||||
|
color: var(--foreground);
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
border: 1px solid var(--foreground);
|
||||||
|
color: var(--foreground);
|
||||||
|
height: 12rem;
|
||||||
|
padding: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--blue);
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-bottom: 3px solid var(--blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.tab-markdown-mode {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--foreground);
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
& + a {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippets-title {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-usage-guide {
|
||||||
|
code {
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-left: 2rem;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new-snippet,
|
||||||
|
.apply-and-reload-snippets {
|
||||||
|
@include button.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new-snippet {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-editor {
|
||||||
|
--save-status-color: var(--blue);
|
||||||
|
|
||||||
|
border: 1px solid var(--save-status-color);
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&.unsaved-changes {
|
||||||
|
--save-status-color: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.to-be-removed {
|
||||||
|
--save-status-color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
border: 1px solid var(--blue);
|
||||||
|
color: var(--foreground);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-controls {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template-columns: 6rem auto max-content max-content;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-enabled,
|
||||||
|
.snippet-in-dropdown {
|
||||||
|
border: 1px solid var(--blue);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-markdown {
|
||||||
|
height: 12rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-remove,
|
||||||
|
.snippet-save {
|
||||||
|
@include button.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-save {
|
||||||
|
--button-accent: var(--yellow);
|
||||||
|
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippet-remove {
|
||||||
|
&.to-be-removed {
|
||||||
|
--button-color: var(--foreground);
|
||||||
|
|
||||||
|
color: var(--background-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,5 +8,9 @@
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab.tab-markdown-mode + a {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue