Skip to content

Commit

Permalink
feat(font-type): ✨ add new extension FontType
Browse files Browse the repository at this point in the history
  • Loading branch information
Leecason committed Apr 2, 2020
1 parent 11d187f commit 0c5bd28
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ All available extensions:
- `Print` (New)
- `Fullscreen` (New)
- `SelectAll` (New)
- `FontType` (New)

You can customize the extension menu button view

Expand Down Expand Up @@ -495,7 +496,7 @@ I'm continuously working to add in new features 💪.
- [x] demo page
- [x] `Table` extension
- [x] `Iframe` extension
- [ ] `FontFamily` extension
- [x] `FontType` extension
- [ ] `FontSize` extension
- [x] `TextColor` extension
- [x] `TextHighlight` extension
Expand Down
3 changes: 2 additions & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export default {
- `Print` (新)
- `Fullscreen` (新)
- `SelectAll` (新)
- `FontType` (新)

你可以自定义菜单按钮的渲染视图

Expand Down Expand Up @@ -490,7 +491,7 @@ _OR_
- [x] demo 页面
- [x] `Table` extension
- [x] `Iframe` extension
- [ ] `FontFamily` extension
- [x] `FontType` extension
- [ ] `FontSize` extension
- [x] `TextColor` extension
- [x] `TextHighlight` extension
Expand Down
2 changes: 2 additions & 0 deletions examples/views/AllExtensions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Italic,
Strike,
Code,
FontType,
TextColor,
TextHighlight,
FormatClear,
Expand Down Expand Up @@ -72,6 +73,7 @@ export default {
new Italic({ bubble: true }),
new Strike({ bubble: true }),
new Code(),
new FontType(),
new TextColor({ bubble: true }),
new TextHighlight({ bubble: true }),
new FormatClear(),
Expand Down
1 change: 1 addition & 0 deletions src/components/MenuCommands/CommandButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import Icon from 'vue-awesome/components/Icon.vue';
import 'vue-awesome/icons/heading';
import 'vue-awesome/icons/font';
import 'vue-awesome/icons/tint';
import 'vue-awesome/icons/highlighter';
import 'vue-awesome/icons/bold';
import 'vue-awesome/icons/underline';
Expand Down
86 changes: 86 additions & 0 deletions src/components/MenuCommands/FontTypeDropdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<template>
<el-dropdown
placement="bottom"
trigger="click"
@command="toggleFontType"
>
<command-button
:tooltip="t('editor.extensions.FontType.tooltip')"
icon="font"
/>

<el-dropdown-menu
slot="dropdown"
class="el-tiptap-dropdown-menu"
>
<el-dropdown-item
v-for="name in fontTypes"
:key="name"
:command="name"
:class="{
'el-tiptap-dropdown-menu__item--active': name === activeFontType,
}"
class="el-tiptap-dropdown-menu__item"
>
<span
:data-font="name"
:style="{ 'font-family': name }"
>{{ name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>

<script lang="ts">
import { Component, Prop, Mixins } from 'vue-property-decorator';
import { MenuData } from 'tiptap';
import { Dropdown, DropdownMenu, DropdownItem } from 'element-ui';
import i18nMixin from '@/mixins/i18nMixin';
import { DEFAULT_FONT_TYPE_MAP, findActiveFontType } from '@/utils/font_type';
import { isPlainObject } from '@/utils/shared';
import Logger from '@/utils/logger';
import CommandButton from './CommandButton.vue';
@Component({
components: {
[Dropdown.name]: Dropdown,
[DropdownMenu.name]: DropdownMenu,
[DropdownItem.name]: DropdownItem,
CommandButton,
},
})
export default class FontTypeDropdown extends Mixins(i18nMixin) {
@Prop({
type: Object,
required: true,
})
readonly editorContext!: MenuData;
private get editor () {
return this.editorContext.editor;
}
private get fontTypes () {
const { fontTypes } = this.editor.extensions.options.font_type;
if (!isPlainObject(fontTypes)) {
Logger.error('\'fontTypes\' should be an object');
return DEFAULT_FONT_TYPE_MAP;
}
return fontTypes;
}
private get activeFontType (): string {
return findActiveFontType(this.editor.state);
}
private toggleFontType (name: string) {
if (name === this.activeFontType) {
this.editorContext.commands.font_type('');
} else {
this.editorContext.commands.font_type(name);
}
}
};
</script>
73 changes: 73 additions & 0 deletions src/extensions/font_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// @ts-nocheck
import { Mark, MenuData } from 'tiptap';
import { CommandFunction } from 'tiptap-commands';
import { Node as ProsemirrorNode, MarkType } from 'prosemirror-model';
import { MenuBtnView } from '@/../types';
import FontTypeDropdown from '@/components/MenuCommands/FontTypeDropdown.vue';
import { DEFAULT_FONT_TYPE_MAP, setFontType } from '@/utils/font_type';

export default class FontType extends Mark implements MenuBtnView {
get name () {
return 'font_type';
}

get defaultOptions () {
return {
fontTypes: DEFAULT_FONT_TYPE_MAP,
};
}

get schema () {
return {
attrs: {
name: '',
},
inline: true,
group: 'inline',
parseDOM: [
{
style: 'font-family',
getAttrs: (name: string) => {
return {
name: name ? name.replace(/["']/g, '') : '',
};
},
},
],
toDOM (node: ProsemirrorNode) {
const { name } = node.attrs;
const attrs: { style?: string } = {};

if (name) {
attrs.style = `font-family: ${name}`;
}
return ['span', attrs, 0];
},
};
}

commands ({ type }: { type: MarkType }) {
return (name: string): CommandFunction => (state, dispatch) => {
let { tr } = state;
tr = setFontType(
state.tr.setSelection(state.selection),
type,
name,
);
if (tr.docChanged || tr.storedMarksSet) {
dispatch && dispatch(tr);
return true;
}
return false;
};
}

menuBtnView (editorContext: MenuData) {
return {
component: FontTypeDropdown,
componentProps: {
editorContext,
},
};
}
}
1 change: 1 addition & 0 deletions src/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export { default as Strike } from './strike';
export { default as Link } from './link';
export { default as TextColor } from './text_color';
export { default as TextHighlight } from './text_highlight';
export { default as FontType } from './font_type';

// extensions
export { default as HorizontalRule } from './horizontal_rule';
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/text_color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default class TextColor extends Mark implements MenuBtnView {
colorSet: this.options.colors,
selectedColor: getMarkAttrs('text_color').color,
tooltip: t('editor.extensions.TextColor.tooltip'),
icon: 'font',
icon: 'tint',
},
componentEvents: {
confirm: (color: string) => commands.text_color(color),
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/de/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export default {
},
},
},
FontType: {
tooltip: 'Schriftfamilie',
},
TextColor: {
tooltip: 'Textfarbe',
},
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export default {
},
},
},
FontType: {
tooltip: 'Font family',
},
TextColor: {
tooltip: 'Text color',
},
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/pl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export default {
},
},
},
FontType: {
tooltip: 'Rodzina czcionek',
},
TextColor: {
tooltip: 'Kolor tekstu',
},
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/ru/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export default {
},
},
},
FontType: {
tooltip: 'Семейство шрифтов',
},
TextColor: {
tooltip: 'Цвет текста',
},
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/zh/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export default {
},
},
},
FontType: {
tooltip: '字体',
},
TextColor: {
tooltip: '文本颜色',
},
Expand Down
68 changes: 68 additions & 0 deletions src/utils/font_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// @ts-ignore
import { getMarkAttrs } from 'tiptap-utils';
import { Transaction, TextSelection, AllSelection, EditorState } from 'prosemirror-state';
import { Mark as ProsemirrorMark, MarkType } from 'prosemirror-model';
import applyMark from './apply_mark';

const DEFAULT_FONT_TYPE_NAMES = [
'Arial',
'Arial Black',
'Georgia',
'Impact',
'Tahoma',
'Times New Roman',
'Verdana',
'Courier New',
'Lucida Console',
'Monaco',
'monospace',
];

export const DEFAULT_FONT_TYPE_MAP = DEFAULT_FONT_TYPE_NAMES.reduce((obj: { [key: string]: string }, type: string) => {
obj[type] = type;
return obj;
}, {});

export function setFontType (tr: Transaction, type: MarkType, name: string): Transaction {
const { selection } = tr;

if (!(selection instanceof TextSelection || selection instanceof AllSelection)) {
return tr;
}
const attrs = name ? { name } : null;
tr = applyMark(tr, type, attrs);
return tr;
}

const DEFAULT_FONT_TYPE = '';

export function findActiveFontType (state: EditorState): string {
const { schema, selection, tr } = state;
const markType = schema.marks.font_type;

if (!markType) return DEFAULT_FONT_TYPE;

const { empty } = selection;

if (empty) {
const storedMarks = tr.storedMarks ||
state.storedMarks ||
(
selection instanceof TextSelection &&
selection.$cursor &&
selection.$cursor.marks &&
selection.$cursor.marks()
) ||
[];

const sm = storedMarks.find((m: ProsemirrorMark) => m.type === markType);
return (sm && sm.attrs.name) || DEFAULT_FONT_TYPE;
}

const attrs = getMarkAttrs(state, markType);
const fontName = attrs.name;

if (!fontName) return DEFAULT_FONT_TYPE;

return fontName;
}
8 changes: 8 additions & 0 deletions src/utils/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ export function cached (fn: Function): Function {
export const capitalize = cached((str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1);
});

/**
* Strict object type check. Only returns true
* for plain JavaScript objects.
*/
export function isPlainObject (obj: any): boolean {
return Object.prototype.toString.call(obj) === '[object Object]';
}

0 comments on commit 0c5bd28

Please sign in to comment.