Skip to content

Commit

Permalink
Merge branch 'walinejs:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mapxn authored Jan 15, 2025
2 parents 0facd4c + 742c7d7 commit af9bd82
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 160 deletions.
113 changes: 55 additions & 58 deletions packages/client/src/components/ArticleReaction.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<script setup lang="ts">
import { getArticleCounter, updateArticleCounter } from '@waline/api';
import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue';
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import { LoadingIcon } from './Icons.js';
import { useReactionStorage } from '../composables/index.js';
import type { WalineReactionLocale } from '../typings/index.js';
import { configKey } from '../config/index.js';
defineExpose();
import { watchImmediate } from '@vueuse/core';
interface ReactionItem {
icon: string;
Expand All @@ -25,102 +24,100 @@ const voteNumbers = ref<number[]>([]);
const locale = computed(() => config.value.locale);
const isReactionEnabled = computed(() => config.value.reaction.length > 0);
const reactionsInfo = computed<ReactionItem[]>(() => {
const reactionsInfo = computed<ReactionItem[] | null>(() => {
const { reaction, path } = config.value;
if (!reaction.length) return null;
return reaction.map((icon, index) => ({
icon,
desc: locale.value[`reaction${index}` as keyof WalineReactionLocale],
active: reactionStorage.value[path] === index,
}));
});
let abort: () => void;
let abort: (() => void) | undefined;
const fetchReaction = async (): Promise<void> => {
if (!isReactionEnabled.value) {
return;
}
if (!isReactionEnabled.value) return;
const { serverURL, lang, path, reaction } = config.value;
const controller = new AbortController();
abort = controller.abort.bind(controller);
const resp = (await getArticleCounter({
const [reactionData] = (await getArticleCounter({
serverURL,
lang,
paths: [path],
type: reaction.map((_reaction, index) => `reaction${index}`),
type: reaction.map((_, index) => `reaction${index}`),
signal: controller.signal,
})) as Record<string, number>[];
voteNumbers.value = reaction.map(
(_reaction, index) => resp[0][`reaction${index}`],
(_, index) => reactionData[`reaction${index}`],
);
};
const vote = async (index: number): Promise<void> => {
// we should ensure that only one vote request is sent at a time
if (votingIndex.value === -1) {
const { serverURL, lang, path } = config.value;
const currentVoteItemIndex = reactionStorage.value[path];
// mark voting status
votingIndex.value = index;
// if user already vote current article, decrease the voted item number
if (currentVoteItemIndex !== undefined) {
await updateArticleCounter({
serverURL,
lang,
path,
type: `reaction${currentVoteItemIndex}`,
action: 'desc',
});
voteNumbers.value[currentVoteItemIndex] = Math.max(
voteNumbers.value[currentVoteItemIndex] - 1,
0,
);
}
// increase voting number if current reaction item is not been voted
if (currentVoteItemIndex !== index) {
await updateArticleCounter({
serverURL,
lang,
path,
type: `reaction${index}`,
});
voteNumbers.value[index] = (voteNumbers.value[index] || 0) + 1;
}
// update vote info in local storage
if (currentVoteItemIndex === index) delete reactionStorage.value[path];
else reactionStorage.value[path] = index;
// voting is completed
votingIndex.value = -1;
if (votingIndex.value !== -1) return;
const { serverURL, lang, path } = config.value;
const currentVoteItemIndex = reactionStorage.value[path];
// mark voting status
votingIndex.value = index;
// if user already vote current article, decrease the voted item number
if (currentVoteItemIndex !== undefined) {
await updateArticleCounter({
serverURL,
lang,
path,
type: `reaction${currentVoteItemIndex}`,
action: 'desc',
});
voteNumbers.value[currentVoteItemIndex] = Math.max(
voteNumbers.value[currentVoteItemIndex] - 1,
0,
);
}
// increase voting number if current reaction item is not been voted
if (currentVoteItemIndex !== index) {
await updateArticleCounter({
serverURL,
lang,
path,
type: `reaction${index}`,
});
voteNumbers.value[index] = (voteNumbers.value[index] || 0) + 1;
}
// update vote info in local storage
if (currentVoteItemIndex === index) delete reactionStorage.value[path];
else reactionStorage.value[path] = index;
// voting is completed
votingIndex.value = -1;
};
onMounted(() => {
watch(
watchImmediate(
() => [config.value.serverURL, config.value.path],
() => {
void fetchReaction();
},
{ immediate: true },
() => fetchReaction(),
);
});
onUnmounted(() => {
abort();
abort?.();
});
</script>

<template>
<div v-if="reactionsInfo.length" class="wl-reaction">
<div v-if="reactionsInfo" class="wl-reaction">
<div class="wl-reaction-title" v-text="locale.reactionTitle" />

<ul class="wl-reaction-list">
Expand Down
93 changes: 43 additions & 50 deletions packages/client/src/components/CommentBox.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useDebounceFn, useEventListener } from '@vueuse/core';
import { useDebounceFn, useEventListener, watchImmediate } from '@vueuse/core';
import type { WalineComment, WalineCommentData, UserInfo } from '@waline/api';
import { addComment, login, updateComment } from '@waline/api';
import autosize from 'autosize';
Expand Down Expand Up @@ -39,7 +39,7 @@ import type {
} from '../typings/index.js';
import type { WalineEmojiConfig } from '../utils/index.js';
import {
getEmojis,
getEmojisInfo,
getImageFromDataTransfer,
getWordNumber,
isValidEmail,
Expand Down Expand Up @@ -142,15 +142,12 @@ const insert = (content: string): void => {
textArea.scrollTop = scrollTop;
};
const onKeyDown = (event: KeyboardEvent): void => {
if (isSubmitting.value) {
return;
}
const key = event.key;
const onEditorKeyDown = ({ key, ctrlKey, metaKey }: KeyboardEvent): void => {
// avoid submitting same comment multiple times
if (isSubmitting.value) return;
// Shortcut key
if ((event.ctrlKey || event.metaKey) && key === 'Enter') void submitComment();
// submit comment when pressing cmd|ctrl + enter
if ((ctrlKey || metaKey) && key === 'Enter') void submitComment();
};
const uploadImage = (file: File): Promise<void> => {
Expand All @@ -176,7 +173,7 @@ const uploadImage = (file: File): Promise<void> => {
});
};
const onDrop = (event: DragEvent): void => {
const onEditorDrop = (event: DragEvent): void => {
if (event.dataTransfer?.items) {
const file = getImageFromDataTransfer(event.dataTransfer.items);
Expand All @@ -187,15 +184,15 @@ const onDrop = (event: DragEvent): void => {
}
};
const onPaste = (event: ClipboardEvent): void => {
const onEditorPaste = (event: ClipboardEvent): void => {
if (event.clipboardData) {
const file = getImageFromDataTransfer(event.clipboardData.items);
if (file && canUploadImage.value) void uploadImage(file);
}
};
const onChange = (): void => {
const onImageChange = (): void => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const inputElement = imageUploaderRef.value!;
Expand Down Expand Up @@ -437,31 +434,6 @@ const onGifSearch = useDebounceFn((event: Event) => {
void onImageWallScroll(event);
}, 300);
// update wordNumber
watch(
[config, wordNumber],
([config, wordNumber]) => {
const { wordLimit: limit } = config;
if (limit) {
if (wordNumber < limit[0] && limit[0] !== 0) {
wordLimit.value = limit[0];
isWordNumberLegal.value = false;
} else if (wordNumber > limit[1]) {
wordLimit.value = limit[1];
isWordNumberLegal.value = false;
} else {
wordLimit.value = limit[1];
isWordNumberLegal.value = true;
}
} else {
wordLimit.value = 0;
isWordNumberLegal.value = true;
}
},
{ immediate: true },
);
useEventListener('click', popupHandler);
useEventListener(
'message',
Expand All @@ -479,6 +451,27 @@ useEventListener(
},
);
// start tracking comment word number
watchImmediate([config, wordNumber], ([config, wordNumber]) => {
const { wordLimit: limit } = config;
if (limit) {
if (wordNumber < limit[0] && limit[0] !== 0) {
wordLimit.value = limit[0];
isWordNumberLegal.value = false;
} else if (wordNumber > limit[1]) {
wordLimit.value = limit[1];
isWordNumberLegal.value = false;
} else {
wordLimit.value = limit[1];
isWordNumberLegal.value = true;
}
} else {
wordLimit.value = 0;
isWordNumberLegal.value = true;
}
});
// watch gif
watch(showGif, async (showGif) => {
if (!showGif) return;
Expand All @@ -488,11 +481,14 @@ watch(showGif, async (showGif) => {
// clear input
if (gifSearchRef.value) gifSearchRef.value.value = '';
// set loading state
searchResults.loading = true;
// display default results
searchResults.list = await (searchOptions.default?.() ??
searchOptions.search(''));
// clear loading state
searchResults.loading = false;
});
Expand All @@ -502,7 +498,7 @@ onMounted(() => {
}
// watch editor
watch(
watchImmediate(
() => editor.value,
(value) => {
const { highlighter, texRenderer } = config.value;
Expand All @@ -520,17 +516,14 @@ onMounted(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, import-x/no-named-as-default-member
else autosize.destroy(textAreaRef.value!);
},
{ immediate: true },
);
// watch emoji value change
watch(
watchImmediate(
() => config.value.emoji,
(emojiConfig) =>
getEmojis(emojiConfig).then((config) => {
emoji.value = config;
}),
{ immediate: true },
async (emojiConfig) => {
emoji.value = await getEmojisInfo(emojiConfig);
},
);
});
</script>
Expand Down Expand Up @@ -611,9 +604,9 @@ onMounted(() => {
v-model="editor"
class="wl-editor"
:placeholder="replyUser ? `@${replyUser}` : locale.placeholder"
@keydown="onKeyDown"
@drop="onDrop"
@paste="onPaste"
@keydown="onEditorKeyDown"
@drop="onEditorDrop"
@paste="onEditorPaste"
/>

<div v-show="showPreview" class="wl-preview">
Expand Down Expand Up @@ -668,7 +661,7 @@ onMounted(() => {
aria-hidden="true"
type="file"
accept=".png,.jpg,.jpeg,.webp,.bmp,.gif"
@change="onChange"
@change="onImageChange"
/>

<label
Expand Down
2 changes: 0 additions & 2 deletions packages/client/src/components/ImageWall.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ const props = withDefaults(

defineEmits<(event: 'insert', content: string) => void>();

defineExpose();

let resizeObserver: ResizeObserver | null = null;
const wall = useTemplateRef<HTMLDivElement>('wall');
const state = ref<Record<string, boolean>>({});
Expand Down
Loading

0 comments on commit af9bd82

Please sign in to comment.