mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add notifications system and support user mentions in comments (#9861)
* v-menu de/activated onKeyDown. No List yet. * v-list * add user suggestion * uuids replaced * user-popover working * avatars flex row with usernames in suggestions * added space to end of uuid insert * autofocus + move caret to end of last insert * removed unnecessary setTimeout() * fixed filter 500 with ids * better fix * New translations en-US.yaml (French) (#9907) * New translations en-US.yaml (French) (#9912) * New translations en-US.yaml (French) (#9916) * New translations en-US.yaml (Russian) (#9918) * New translations en-US.yaml (Swedish) (#9920) * Email updates (#9921) * add from name for emails * updatd email template style * reset password email copy * updated logo to newest version * update invite email copy * decouple field template logic * push up styling * Start on new v-template-input * Add notifications API endpoints Squashed commit of the following: commit 9d86721ef795d03bc55693c0f99bde8e269d60e9 Merge: b4458c19f34131d06eAuthor: rijkvanzanten <rijkvanzanten@me.com> Date: Mon Nov 22 09:27:43 2021 -0500 Merge branch 'mentions' into mentions-api commit b4458c19f7c54f18fa415fc04c63642c2f5a17b0 Author: rijkvanzanten <rijkvanzanten@me.com> Date: Thu Nov 18 18:34:04 2021 -0500 Remove unused import commit e6a9d36bbfdf95cb18d29336da61ecb14b677934 Author: rijkvanzanten <rijkvanzanten@me.com> Date: Thu Nov 18 18:28:31 2021 -0500 Extract user mentions from comments commit b3e571a2daa287e1740a050096913662a57e9861 Merge: c93b833d2af2a6dd7fAuthor: rijkvanzanten <rijkvanzanten@me.com> Date: Thu Nov 18 17:39:52 2021 -0500 Merge branch 'mentions' into mentions-api commit c93b833d2b848e306c434b370d4e4e11967e85d0 Author: rijkvanzanten <rijkvanzanten@me.com> Date: Thu Nov 18 17:35:45 2021 -0500 Send emails w/ parsed MD commit 64bbd6596f20a07028d2387d60e33dfe4f91c032 Author: rijkvanzanten <rijkvanzanten@me.com> Date: Thu Nov 18 16:18:16 2021 -0500 Add notifications endpoint + permissions commit fba55c02dc9c303a38b1b958350684cccd3dd82c Author: rijkvanzanten <rijkvanzanten@me.com> Date: Thu Nov 18 15:33:28 2021 -0500 Add system data for notifications * push * Make v-template-input work * Add the two-way binding * submit button posting, not clearing text area * comment text area clearing on submit * Replace insertion correctly * Added scope support to LDAP group and user search (#9529) * Added scope support LDAP group and user search * Fixed linter screwing up my markdown * Update docs/configuration/config-options.md * Always return correct DN for user with sub scope * Fix indeterminate meta and schema property in advanded field creation (#9924) * Fix impossibility to save M2M (alterations not triggered) (#9992) * Fix alterations refactor * fix roles aggregate query (#9994) * Update iis.md (#9998) added the IIS URL Rewrite module as a requirement * New translations en-US.yaml (English, United Kingdom) (#10001) * Fix LDAP race condition (#9993) * Fix input ui * Revert changes to v-field-template * Update mentions permissions * Fix linter warnings * Optimize sending flow * Revert "Rename activity->notifications module (#9446)" This reverts commit428e5d4ea9. * Add notifications drawer * Update migrations * Improve constraints * Add email notifications toggle on users * Add docs, fix graphql support * Move caret-pos to devdeps * Remove unused new triggerKeyPressed system * Remove unused use-caret composable Co-authored-by: Nitwel <nitwel@arcor.de> Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com> Co-authored-by: Ben Haynes <ben@rngr.org> Co-authored-by: Aiden Foxx <aiden.foxx@sbab.se> Co-authored-by: Oreille <33065839+Oreilles@users.noreply.github.com> Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com> Co-authored-by: Paul Boudewijn <paul@helderinternet.nl>
This commit is contained in:
@@ -47,6 +47,7 @@ import VSlider from './v-slider/';
|
||||
import VSwitch from './v-switch/';
|
||||
import VTable from './v-table/';
|
||||
import VTabs, { VTab, VTabItem, VTabsItems } from './v-tabs/';
|
||||
import VTemplateInput from './v-template-input.vue';
|
||||
import VTextOverflow from './v-text-overflow.vue';
|
||||
import VTextarea from './v-textarea';
|
||||
import VUpload from './v-upload';
|
||||
@@ -103,6 +104,7 @@ export function registerComponents(app: App): void {
|
||||
app.component('VTable', VTable);
|
||||
app.component('VTabsItems', VTabsItems);
|
||||
app.component('VTabs', VTabs);
|
||||
app.component('VTemplateInput', VTemplateInput);
|
||||
app.component('VTextarea', VTextarea);
|
||||
app.component('VTextOverflow', VTextOverflow);
|
||||
app.component('VUpload', VUpload);
|
||||
|
||||
@@ -206,6 +206,7 @@ body {
|
||||
--content-padding: 16px;
|
||||
--content-padding-bottom: 32px;
|
||||
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export default defineComponent({
|
||||
trigger: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: (val: string) => ['hover', 'click'].includes(val),
|
||||
validator: (val: string) => ['hover', 'click', 'keyDown'].includes(val),
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
|
||||
177
app/src/components/v-template-input.vue
Normal file
177
app/src/components/v-template-input.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div
|
||||
ref="input"
|
||||
class="v-template-input"
|
||||
:class="{ multiline }"
|
||||
contenteditable="true"
|
||||
tabindex="1"
|
||||
@input="processText"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watch, onMounted } from 'vue';
|
||||
import { position } from 'caret-pos';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
captureGroup: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
multiline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerCharacter: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
items: {
|
||||
type: Object as PropType<Record<string, string>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'trigger', 'deactivate'],
|
||||
setup(props, { emit }) {
|
||||
const input = ref<HTMLDivElement>();
|
||||
|
||||
let hasTriggered = false;
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newText) => {
|
||||
if (!input.value) return;
|
||||
|
||||
if (newText !== input.value.innerText) {
|
||||
parseHTML(newText);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.modelValue && props.modelValue !== input.value!.innerText) {
|
||||
parseHTML(props.modelValue);
|
||||
}
|
||||
});
|
||||
|
||||
return { processText, input };
|
||||
|
||||
function processText(event: KeyboardEvent) {
|
||||
const input = event.target as HTMLDivElement;
|
||||
|
||||
const caretPos = position(input).pos;
|
||||
|
||||
const text = input.innerText ?? '';
|
||||
|
||||
let endPos = text.indexOf(' ', caretPos);
|
||||
if (endPos == -1) endPos = text.length;
|
||||
const result = /\S+$/.exec(text.slice(0, endPos));
|
||||
let word = result ? result[0] : null;
|
||||
if (word) word = word.replace(/[\s'";:,./?\\-]$/, '');
|
||||
|
||||
if (word?.startsWith(props.triggerCharacter)) {
|
||||
emit('trigger', { searchQuery: word.substring(props.triggerCharacter.length), caretPosition: caretPos });
|
||||
hasTriggered = true;
|
||||
} else {
|
||||
if (hasTriggered) {
|
||||
emit('deactivate');
|
||||
hasTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
parseHTML();
|
||||
|
||||
emit('update:modelValue', input.innerText);
|
||||
}
|
||||
|
||||
function parseHTML(innerText?: string) {
|
||||
if (!input.value) return;
|
||||
|
||||
let newHTML = innerText ?? input.value.innerHTML ?? '';
|
||||
|
||||
const caretPos = window.getSelection()?.rangeCount ? position(input.value).pos : 0;
|
||||
|
||||
const matches = newHTML.match(new RegExp(`${props.captureGroup}(?!</mark>)`, 'gi'));
|
||||
|
||||
if (matches) {
|
||||
for (const match of matches ?? []) {
|
||||
newHTML = newHTML.replace(
|
||||
new RegExp(`(${match})(?!</mark>)`),
|
||||
` <mark class="preview" data-preview="${
|
||||
props.items[match.substring(props.triggerCharacter.length)]
|
||||
}" contenteditable="false">${match}</mark> `
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.value.innerHTML !== newHTML) {
|
||||
input.value.innerHTML = newHTML;
|
||||
const delta = newHTML.length - input.value.innerHTML.length;
|
||||
|
||||
const newPosition = caretPos + delta;
|
||||
|
||||
if (newPosition >= newHTML.length || newPosition < 0) {
|
||||
position(input.value, newHTML.length - 1);
|
||||
} else {
|
||||
position(input.value, caretPos + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.v-template-input {
|
||||
position: relative;
|
||||
height: var(--input-height);
|
||||
padding: var(--input-padding);
|
||||
padding-bottom: 32px;
|
||||
overflow: hidden;
|
||||
color: var(--foreground-normal);
|
||||
font-family: var(--family-sans-serif);
|
||||
white-space: nowrap;
|
||||
background-color: var(--background-page);
|
||||
border: var(--border-width) solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
transition: border-color var(--fast) var(--transition);
|
||||
|
||||
&.multiline {
|
||||
height: var(--input-height-tall);
|
||||
overflow-y: auto;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--border-normal-alt);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
:deep(.preview) {
|
||||
display: inline-block;
|
||||
margin: 2px;
|
||||
padding: 2px 4px;
|
||||
color: var(--primary);
|
||||
font-size: 0;
|
||||
line-height: 1;
|
||||
vertical-align: -2px;
|
||||
background: var(--primary-alt);
|
||||
border-radius: var(--border-radius);
|
||||
user-select: text;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
content: attr(data-preview);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user