Merge pull request #1134 from hgiesel/formatblock

Add block formatting options to Editor
This commit is contained in:
Damien Elmes 2021-04-17 09:40:52 +10:00 committed by GitHub
commit f869148d5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 59 deletions

View file

@ -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.

View file

@ -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
View 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[];
}

View file

@ -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} />

View file

@ -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}

View file

@ -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>

View file

@ -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}

View file

@ -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>

View 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],
});
}

View file

@ -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,

View file

@ -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,

View file

@ -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);