mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge pull request #1134 from hgiesel/formatblock
Add block formatting options to Editor
This commit is contained in:
commit
f869148d5e
12 changed files with 248 additions and 59 deletions
|
@ -1,8 +1,11 @@
|
|||
editing-add-media = Add Media
|
||||
editing-align-left = Align left
|
||||
editing-align-right = Align right
|
||||
editing-an-error-occurred-while-opening = An error occurred while opening { $val }
|
||||
editing-attach-picturesaudiovideo-f3 = Attach pictures/audio/video (F3)
|
||||
editing-bold-text-ctrlandb = Bold text (Ctrl+B)
|
||||
editing-cards = Cards
|
||||
editing-center = Center
|
||||
editing-change-colour-f8 = Change colour (F8)
|
||||
editing-cloze-deletion-ctrlandshiftandc = Cloze deletion (Ctrl+Shift+C)
|
||||
editing-couldnt-record-audio-have-you-installed = Couldn't record audio. Have you installed 'lame'?
|
||||
|
@ -13,8 +16,10 @@ editing-edit-current = Edit Current
|
|||
editing-edit-html = Edit HTML
|
||||
editing-fields = Fields
|
||||
editing-html-editor = HTML Editor
|
||||
editing-indent = Indent
|
||||
editing-italic-text-ctrlandi = Italic text (Ctrl+I)
|
||||
editing-jump-to-tags-with-ctrlandshiftandt = Jump to tags with Ctrl+Shift+T
|
||||
editing-justify = Justify
|
||||
editing-latex = LaTeX
|
||||
editing-latex-equation = LaTeX equation
|
||||
editing-latex-math-env = LaTeX math env.
|
||||
|
@ -22,6 +27,8 @@ editing-mathjax-block = MathJax block
|
|||
editing-mathjax-chemistry = MathJax chemistry
|
||||
editing-mathjax-inline = MathJax inline
|
||||
editing-media = Media
|
||||
editing-ordered-list = Ordered list
|
||||
editing-outdent = Outdent
|
||||
editing-paste = Paste
|
||||
editing-record-audio-f5 = Record audio (F5)
|
||||
editing-remove-formatting-ctrlandr = Remove formatting (Ctrl+R)
|
||||
|
@ -32,4 +39,5 @@ editing-superscript-ctrlandand = Superscript (Ctrl++)
|
|||
editing-tags = Tags
|
||||
editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing note, you need to change it to a cloze type first, via 'Notes>Change Note Type'
|
||||
editing-underline-text-ctrlandu = Underline text (Ctrl+U)
|
||||
editing-unordered-list = Unordered list
|
||||
editing-warning-cloze-deletions-will-not-work = Warning, cloze deletions will not work until you switch the type at the top to Cloze.
|
||||
|
|
|
@ -67,6 +67,7 @@ ts_library(
|
|||
copy_bootstrap_icons(
|
||||
name = "bootstrap-icons",
|
||||
icons = [
|
||||
# inline formatting
|
||||
"type-bold.svg",
|
||||
"type-italic.svg",
|
||||
"type-underline.svg",
|
||||
|
@ -74,6 +75,17 @@ copy_bootstrap_icons(
|
|||
"square-fill.svg",
|
||||
"paperclip.svg",
|
||||
"mic.svg",
|
||||
|
||||
# block formatting
|
||||
"list-ul.svg",
|
||||
"list-ol.svg",
|
||||
"text-paragraph.svg",
|
||||
"justify.svg",
|
||||
"text-left.svg",
|
||||
"text-right.svg",
|
||||
"text-center.svg",
|
||||
"text-indent-left.svg",
|
||||
"text-indent-right.svg",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
9
ts/editor-toolbar/ButtonDropdown.d.ts
vendored
Normal file
9
ts/editor-toolbar/ButtonDropdown.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
import type { ToolbarItem } from "./types";
|
||||
|
||||
export interface ButtonDropdownProps {
|
||||
id: string;
|
||||
className?: string;
|
||||
buttons: ToolbarItem[];
|
||||
}
|
|
@ -10,10 +10,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
export let className = "";
|
||||
|
||||
function extendClassName(className: string): string {
|
||||
return `dropdown-menu bg-transparent border-0 ${className}`;
|
||||
return `dropdown-menu btn-dropdown-menu py-1 mb-0 ${className}`;
|
||||
}
|
||||
|
||||
export let buttons: ToolbarItem[];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:global(.btn-dropdown-menu) {
|
||||
background-color: var(--window-bg);
|
||||
border-color: var(--medium-border);
|
||||
}
|
||||
</style>
|
||||
|
||||
<ButtonGroup {id} className={extendClassName(className)} {buttons} />
|
||||
|
|
|
@ -20,27 +20,25 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
<style lang="scss">
|
||||
ul {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
justify-items: start;
|
||||
|
||||
flex-wrap: var(--toolbar-wrap);
|
||||
overflow-y: auto;
|
||||
|
||||
padding-inline-start: 0;
|
||||
margin-bottom: 0;
|
||||
margin: 0 0 calc(var(--toolbar-size) / 10);
|
||||
}
|
||||
|
||||
.border-group {
|
||||
/* buttons' borders exactly overlap each other */
|
||||
.border-overlap-group {
|
||||
:global(button),
|
||||
:global(select) {
|
||||
margin-left: -2px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-bottom: calc(var(--toolbar-size) / 15);
|
||||
|
||||
> :global(button),
|
||||
> :global(select) {
|
||||
|
@ -68,16 +66,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
}
|
||||
|
||||
&:not(:nth-child(1)) {
|
||||
&.gap-item:not(:nth-child(1)) {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul {id} class={className} class:border-group={!nightMode}>
|
||||
<ul {id} class={className} class:border-overlap-group={!nightMode}>
|
||||
{#each buttons as button}
|
||||
{#if !button.hidden}
|
||||
<li>
|
||||
<li class:gap-item={nightMode}>
|
||||
<svelte:component this={button.component} {...filterHidden(button)} />
|
||||
</li>
|
||||
{/if}
|
||||
|
|
|
@ -18,30 +18,33 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "ts/sass/bootstrap/functions";
|
||||
@import "ts/sass/bootstrap/variables";
|
||||
@use 'ts/sass/button_mixins' as button;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.btn-day {
|
||||
color: black;
|
||||
|
||||
&.nightMode {
|
||||
&:active {
|
||||
background-color: button.$focus-color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: black;
|
||||
}
|
||||
.btn-night {
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
color: white;
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
@include button.btn-night-base;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
&:active {
|
||||
background-color: button.$focus-color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +61,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<button
|
||||
{id}
|
||||
class={`btn dropdown-item ${className}`}
|
||||
class:nightMode
|
||||
class:btn-day={!nightMode}
|
||||
class:btn-night={nightMode}
|
||||
title={tooltip}
|
||||
on:click={onClick}
|
||||
on:mousedown|preventDefault>
|
||||
|
|
|
@ -14,21 +14,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "ts/sass/bootstrap/functions";
|
||||
@import "ts/sass/bootstrap/variables";
|
||||
@use 'ts/sass/button_mixins' as button;
|
||||
|
||||
ul {
|
||||
background-color: $light;
|
||||
background-color: white;
|
||||
border-color: var(--medium-border);
|
||||
}
|
||||
|
||||
&.nightMode {
|
||||
background-color: $secondary;
|
||||
}
|
||||
.night-mode {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul {id} class="dropdown-menu" class:nightMode>
|
||||
<ul {id} class="dropdown-menu" class:night-mode={nightMode}>
|
||||
{#each menuItems as menuItem}
|
||||
<li class:nightMode>
|
||||
<li>
|
||||
<svelte:component this={menuItem.component} {...menuItem} />
|
||||
</li>
|
||||
{/each}
|
||||
|
|
|
@ -77,5 +77,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</div>
|
||||
|
||||
<nav {style}>
|
||||
<ButtonGroup buttons={_buttons} />
|
||||
<ButtonGroup buttons={_buttons} className="mt-0" />
|
||||
</nav>
|
||||
|
|
137
ts/editor-toolbar/formatBlock.ts
Normal file
137
ts/editor-toolbar/formatBlock.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
import ButtonGroup from "./ButtonGroup.svelte";
|
||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
||||
import ButtonDropdown from "./ButtonDropdown.svelte";
|
||||
import type { ButtonDropdownProps } from "./ButtonDropdown";
|
||||
import WithDropdownMenu from "./WithDropdownMenu.svelte";
|
||||
import type { WithDropdownMenuProps } from "./WithDropdownMenu";
|
||||
|
||||
import CommandIconButton from "./CommandIconButton.svelte";
|
||||
import type { CommandIconButtonProps } from "./CommandIconButton";
|
||||
import IconButton from "./IconButton.svelte";
|
||||
import type { IconButtonProps } from "./IconButton";
|
||||
|
||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
||||
import * as tr from "anki/i18n";
|
||||
|
||||
import ulIcon from "./list-ul.svg";
|
||||
import olIcon from "./list-ol.svg";
|
||||
import listOptionsIcon from "./text-paragraph.svg";
|
||||
|
||||
import justifyFullIcon from "./justify.svg";
|
||||
import justifyLeftIcon from "./text-left.svg";
|
||||
import justifyRightIcon from "./text-right.svg";
|
||||
import justifyCenterIcon from "./text-center.svg";
|
||||
|
||||
import indentIcon from "./text-indent-left.svg";
|
||||
import outdentIcon from "./text-indent-right.svg";
|
||||
|
||||
const commandIconButton = dynamicComponent<
|
||||
typeof CommandIconButton,
|
||||
CommandIconButtonProps
|
||||
>(CommandIconButton);
|
||||
|
||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
||||
const buttonDropdown = dynamicComponent<typeof ButtonDropdown, ButtonDropdownProps>(
|
||||
ButtonDropdown
|
||||
);
|
||||
|
||||
const withDropdownMenu = dynamicComponent<
|
||||
typeof WithDropdownMenu,
|
||||
WithDropdownMenuProps
|
||||
>(WithDropdownMenu);
|
||||
|
||||
export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDropdown> &
|
||||
ButtonDropdownProps)[] {
|
||||
const justifyLeftButton = commandIconButton({
|
||||
icon: justifyLeftIcon,
|
||||
command: "justifyLeft",
|
||||
tooltip: tr.editingAlignLeft(),
|
||||
});
|
||||
|
||||
const justifyCenterButton = commandIconButton({
|
||||
icon: justifyCenterIcon,
|
||||
command: "justifyCenter",
|
||||
tooltip: tr.editingCenter(),
|
||||
});
|
||||
|
||||
const justifyRightButton = commandIconButton({
|
||||
icon: justifyRightIcon,
|
||||
command: "justifyRight",
|
||||
tooltip: tr.editingAlignRight(),
|
||||
});
|
||||
|
||||
const justifyFullButton = commandIconButton({
|
||||
icon: justifyFullIcon,
|
||||
command: "justifyFull",
|
||||
tooltip: tr.editingJustify(),
|
||||
});
|
||||
|
||||
const justifyGroup = buttonGroup({
|
||||
id: "justify",
|
||||
buttons: [
|
||||
justifyLeftButton,
|
||||
justifyCenterButton,
|
||||
justifyRightButton,
|
||||
justifyFullButton,
|
||||
],
|
||||
});
|
||||
|
||||
const outdentButton = commandIconButton({
|
||||
icon: outdentIcon,
|
||||
command: "outdent",
|
||||
tooltip: tr.editingOutdent(),
|
||||
activatable: false,
|
||||
});
|
||||
|
||||
const indentButton = commandIconButton({
|
||||
icon: indentIcon,
|
||||
command: "indent",
|
||||
tooltip: tr.editingIndent(),
|
||||
activatable: false,
|
||||
});
|
||||
|
||||
const indentationGroup = buttonGroup({
|
||||
id: "indentation",
|
||||
buttons: [outdentButton, indentButton],
|
||||
});
|
||||
|
||||
const formattingOptions = buttonDropdown({
|
||||
id: "listFormatting",
|
||||
buttons: [justifyGroup, indentationGroup],
|
||||
});
|
||||
|
||||
return [formattingOptions];
|
||||
}
|
||||
|
||||
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
|
||||
|
||||
export function getFormatBlockGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||
ButtonGroupProps {
|
||||
const ulButton = commandIconButton({
|
||||
icon: ulIcon,
|
||||
command: "insertUnorderedList",
|
||||
tooltip: tr.editingUnorderedList(),
|
||||
});
|
||||
|
||||
const olButton = commandIconButton({
|
||||
icon: olIcon,
|
||||
command: "insertOrderedList",
|
||||
tooltip: tr.editingOrderedList(),
|
||||
});
|
||||
|
||||
const listFormattingButton = iconButton({
|
||||
icon: listOptionsIcon,
|
||||
});
|
||||
|
||||
const listFormatting = withDropdownMenu({
|
||||
button: listFormattingButton,
|
||||
menuId: "listFormatting",
|
||||
});
|
||||
|
||||
return buttonGroup({
|
||||
id: "formatBlock",
|
||||
buttons: [ulButton, olButton, listFormatting],
|
||||
});
|
||||
}
|
|
@ -21,7 +21,7 @@ const commandIconButton = dynamicComponent<
|
|||
>(CommandIconButton);
|
||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
||||
|
||||
export function getFormatGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||
export function getFormatInlineGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||
ButtonGroupProps {
|
||||
const boldButton = commandIconButton({
|
||||
icon: boldIcon,
|
||||
|
@ -61,7 +61,7 @@ export function getFormatGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
|||
});
|
||||
|
||||
return buttonGroup({
|
||||
id: "format",
|
||||
id: "formatInline",
|
||||
buttons: [
|
||||
boldButton,
|
||||
italicButton,
|
|
@ -16,7 +16,8 @@ import { setupI18n, ModuleName } from "anki/i18n";
|
|||
import "./bootstrap.css";
|
||||
|
||||
import { getNotetypeGroup } from "./notetype";
|
||||
import { getFormatGroup } from "./format";
|
||||
import { getFormatInlineGroup } from "./formatInline";
|
||||
import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock";
|
||||
import { getColorGroup } from "./color";
|
||||
import { getTemplateGroup, getTemplateMenus } from "./template";
|
||||
import { Identifiable, search, add, insert } from "./identifiable";
|
||||
|
@ -60,11 +61,12 @@ class EditorToolbar extends HTMLElement {
|
|||
setupI18n({ modules: [ModuleName.EDITING] }).then(() => {
|
||||
const buttons = writable([
|
||||
getNotetypeGroup(),
|
||||
getFormatGroup(),
|
||||
getFormatInlineGroup(),
|
||||
getFormatBlockGroup(),
|
||||
getColorGroup(),
|
||||
getTemplateGroup(),
|
||||
]);
|
||||
const menus = writable([...getTemplateMenus()]);
|
||||
const menus = writable([...getTemplateMenus(), ...getFormatBlockMenus()]);
|
||||
|
||||
this.component = new EditorToolbarSvelte({
|
||||
target: this,
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
@import "ts/sass/bootstrap/functions";
|
||||
@import "ts/sass/bootstrap/variables";
|
||||
|
||||
$btn-base-color-day: white;
|
||||
|
||||
@mixin btn-day-base {
|
||||
color: var(--text-fg);
|
||||
background-color: $btn-base-color-day;
|
||||
border-color: var(--medium-border) !important;
|
||||
}
|
||||
|
||||
@mixin btn-day($with-disabled: true) {
|
||||
$base-color: white;
|
||||
|
||||
.btn-day {
|
||||
color: var(--text-fg);
|
||||
background-color: $base-color;
|
||||
border-color: var(--medium-border) !important;
|
||||
|
||||
@content ($base-color);
|
||||
@include btn-day-base;
|
||||
@content ($btn-base-color-day);
|
||||
|
||||
&:hover {
|
||||
background-color: darken($base-color, 8%);
|
||||
background-color: darken($btn-base-color-day, 8%);
|
||||
}
|
||||
|
||||
&:active,
|
||||
|
@ -23,49 +29,55 @@
|
|||
|
||||
@if ($with-disabled) {
|
||||
&[disabled] {
|
||||
background-color: $base-color !important;
|
||||
background-color: $btn-base-color-day !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$btn-base-color-night: #666;
|
||||
|
||||
@mixin btn-night-base {
|
||||
color: var(--text-fg);
|
||||
background-color: $btn-base-color-night;
|
||||
border-color: $btn-base-color-night;
|
||||
}
|
||||
|
||||
@mixin btn-night($with-disabled: true) {
|
||||
$base-color: #666;
|
||||
|
||||
.btn-night {
|
||||
color: var(--text-fg);
|
||||
background-color: $base-color;
|
||||
border-color: $base-color;
|
||||
|
||||
@content ($base-color);
|
||||
@include btn-night-base;
|
||||
@content ($btn-base-color-night);
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($base-color, 8%);
|
||||
border-color: lighten($base-color, 8%);
|
||||
background-color: lighten($btn-base-color-night, 8%);
|
||||
border-color: lighten($btn-base-color-night, 8%);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&.active {
|
||||
@include impressed-shadow(0.35);
|
||||
border-color: darken($base-color, 8%);
|
||||
border-color: darken($btn-base-color-night, 8%);
|
||||
}
|
||||
|
||||
&:active.active {
|
||||
box-shadow: none;
|
||||
border-color: $base-color;
|
||||
border-color: $btn-base-color-night;
|
||||
}
|
||||
|
||||
@if ($with-disabled) {
|
||||
&[disabled] {
|
||||
background-color: $base-color !important;
|
||||
background-color: $btn-base-color-night !important;
|
||||
box-shadow: none !important;
|
||||
border-color: $base-color !important;
|
||||
border-color: $btn-base-color-night !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// should be similar to -webkit-focus-ring-color
|
||||
$focus-color: $blue;
|
||||
|
||||
@mixin impressed-shadow($intensity) {
|
||||
box-shadow: inset 0 calc(var(--toolbar-size) / 15) calc(var(--toolbar-size) / 5)
|
||||
rgba(black, $intensity);
|
||||
|
|
Loading…
Reference in a new issue