Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emoji Picker & Comment improvements #11946

Merged
merged 21 commits into from Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/package.json
Expand Up @@ -107,6 +107,7 @@
"vue-i18n": "9.1.9",
"vue-router": "4.0.12",
"vuedraggable": "4.1.0",
"wellknown": "0.5.0"
"wellknown": "0.5.0",
"@joeattardi/emoji-button": "^4.6.2"
}
}
2 changes: 2 additions & 0 deletions app/src/components/register.ts
Expand Up @@ -53,6 +53,7 @@ import VTextOverflow from './v-text-overflow.vue';
import VTextarea from './v-textarea';
import VUpload from './v-upload';
import VDatePicker from './v-date-picker';
import VEmojiPicker from './v-emoji-picker.vue';

export function registerComponents(app: App): void {
app.component('VAvatar', VAvatar);
Expand Down Expand Up @@ -112,6 +113,7 @@ export function registerComponents(app: App): void {
app.component('VTextOverflow', VTextOverflow);
app.component('VUpload', VUpload);
app.component('VDatePicker', VDatePicker);
app.component('VEmojiPicker', VEmojiPicker);

app.component('TransitionBounce', TransitionBounce);
app.component('TransitionDialog', TransitionDialog);
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/v-avatar/v-avatar.vue
Expand Up @@ -57,7 +57,7 @@ body {
.x-small {
--v-avatar-size: 24px;

border-radius: 2px;
border-radius: 4px;
}

.small {
Expand Down
26 changes: 26 additions & 0 deletions app/src/components/v-emoji-picker.vue
@@ -0,0 +1,26 @@
<template>
<v-button class="emoji-button" x-small secondary icon @click="emojiPicker.togglePicker($event.target as HTMLElement)">
<v-icon name="insert_emoticon" />
</v-button>
</template>

<script setup lang="ts">
import { EmojiButton } from '@joeattardi/emoji-button';
import { onUnmounted } from 'vue';

const emojiPicker = new EmojiButton({
theme: 'auto',
zIndex: 10000,
rijkvanzanten marked this conversation as resolved.
Show resolved Hide resolved
position: 'bottom',
emojisPerRow: 8,
});
const emit = defineEmits(['emoji-selected']);

emojiPicker.on('emoji', (event) => {
emit('emoji-selected', event.emoji);
});

onUnmounted(() => {
emojiPicker.destroyPicker();
});
</script>
13 changes: 12 additions & 1 deletion app/src/components/v-template-input.vue
Expand Up @@ -5,6 +5,7 @@
:class="{ multiline }"
contenteditable="true"
tabindex="1"
:placeholder="placeholder"
@input="processText"
/>
</template>
Expand Down Expand Up @@ -35,6 +36,10 @@ export default defineComponent({
type: Object as PropType<Record<string, string>>,
required: true,
},
placeholder: {
type: String,
default: null,
},
},
emits: ['update:modelValue', 'trigger', 'deactivate', 'up', 'down', 'enter'],
setup(props, { emit }) {
Expand Down Expand Up @@ -314,6 +319,12 @@ export default defineComponent({
border-radius: var(--border-radius);
transition: border-color var(--fast) var(--transition);

&:empty::before {
pointer-events: none;
content: attr(placeholder);
color: var(--foreground-subdued);
}

&.multiline {
height: var(--input-height-tall);
overflow-y: auto;
Expand All @@ -330,7 +341,7 @@ export default defineComponent({

:deep(.preview) {
display: inline-block;
margin: 2px;
margin: 0px;
padding: 2px 4px;
color: var(--primary);
font-size: 0;
Expand Down
18 changes: 11 additions & 7 deletions app/src/directives/markdown.ts
@@ -1,13 +1,17 @@
import { Directive } from 'vue';
import { Directive, DirectiveBinding } from 'vue';
import { md } from '@/utils/md';

const Markdown: Directive = {
beforeMount(el, binding) {
el.innerHTML = md(binding.value ?? '');
},
updated(el, binding) {
el.innerHTML = md(binding.value ?? '');
},
beforeMount: markdown,
updated: markdown,
};

function markdown(el: Element, binding: DirectiveBinding) {
if (typeof binding.value === 'object' && 'value' in binding.value) {
el.innerHTML = md(binding.value.value, binding.value);
} else {
el.innerHTML = md(binding.value ?? '');
}
}

export default Markdown;
1 change: 1 addition & 0 deletions app/src/lang/translations/en-US.yaml
Expand Up @@ -338,6 +338,7 @@ revision_preview: Revision Preview
updates_made: Updates Made
leave_comment: Leave a comment...
post_comment_success: Comment posted
post_comment_updated: Comment updated
item_create_success: Item Created | Items Created
item_update_success: Item Updated | Items Updated
item_delete_success: Item Deleted | Items Deleted
Expand Down
86 changes: 86 additions & 0 deletions app/src/styles/lib/_emoji-picker.scss
@@ -0,0 +1,86 @@
.emoji-picker__wrapper {
.emoji-picker {
--category-button-active-color: var(--primary);
--font: var(--family-sans-serif);
--text-color: var(--foreground-normal);
--dark-text-color: var(--foreground-normal);
--secondary-text-color: var(--foreground-normal-alt);
--dark-secondary-text-color: var(--foreground-normal-alt);

box-shadow: var(--card-shadow);
border-radius: var(--border-radius);
border: var(--border-width) solid var(--border-normal);
background-color: var(--background-page);
}

.emoji-picker__search-container {
height: 40px;
margin: 8px;

.emoji-picker__search-icon {
color: var(--foreground-subdued);
top: calc(50% - 2px);
transform: translate(0, -50%);
}

input.emoji-picker__search {
border-radius: var(--border-radius);
border: var(--border-width) solid var(--border-normal);
background-color: var(--background-page);

height: 100%;

&:hover {
border-color: var(--border-normal-alt);
}

&:focus {
border-color: var(--primary);
}

&::placeholder {
color: var(--foreground-subdued);
}
}
}

.emoji-picker__content {
padding: 8px 0 0 0;
height: unset;

.emoji-picker__category-buttons {
padding: 0 8px;

.emoji-picker__category-button {
transition: color var(--fast) var(--transition);

&:not(.active) {
color: var(--foreground-subdued);
}

&:hover {
color: var(--foreground-normal);
}
}
}

.emoji-picker__category-name {
margin-left: 12px;
}

.emoji-picker__emojis {
border-top: 2px solid var(--border-normal);
}
}

.emoji-picker__preview {
border-top: 2px solid var(--border-normal);
}

.emoji-picker__emoji {
&:hover {
background-color: var(--background-normal);
border-radius: var(--border-radius);
}
}
}
1 change: 1 addition & 0 deletions app/src/styles/main.scss
Expand Up @@ -13,6 +13,7 @@
@import 'lib/highlight';
@import 'lib/fullcalendar';
@import 'lib/frappe-charts';
@import 'lib/emoji-picker';

body.light {
@include light;
Expand Down
20 changes: 18 additions & 2 deletions app/src/utils/md.ts
@@ -1,15 +1,31 @@
import { marked } from 'marked';
import dompurify from 'dompurify';

type Options = {
target: '_blank' | '_self' | '_parent' | '_top';
};

const renderer = new marked.Renderer();

/**
* Render and sanitize a markdown string
*/
export function md(str: string): string {
export function md(str: string, options: Options = { target: '_self' }): string {
dompurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {
node.setAttribute('rel', 'noopener noreferrer');
}
});

return dompurify.sanitize(marked(str), { ADD_ATTR: ['target'] });
renderer.link = function (href, title, text) {
const link = marked.Renderer.prototype.link.apply(this, [href, title, text]);
return link.replace('<a', `<a target="${options.target}"`);
};

return dompurify.sanitize(
marked(str, {
renderer,
}),
{ ADD_ATTR: ['target'] }
);
}