Skip to content

pkp/pkp-lib#11327 Implement public facing UI for user comments #659

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/frontend/components/PkpAccordion/PkpAccordion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const accordionClass = computed(() => ['pkpAccordion']);
width: 100%;
padding: var(--pkp-spacing-2) var(--pkp-spacing-3);
background-color: var(--pkp-background-color-tertiary);
color: var(--pkp-text-color-secondary);
color: var(--pkp-text-color-heading);
font: var(--pkp-font-base-bold);
border: none;
cursor: pointer;
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/components/PkpButton/PkpButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ const props = defineProps({
type: Boolean,
default: false,
},
/** Use when this button represents an action such as delete, go back, revert or cancel. */
isWarnable: Boolean,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will confirm this one with Devika.. maybe we can have just primary and secondary. Goal here will be again just have as least variants as possible.. so its easier to create styling across the themes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any updates on this?

Copy link
Contributor

@jardakotesovec jardakotesovec Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely we will be fine just using the primary&secondary styling. So lets stick with that for now. Primary would be used for delete button, and secondary for cancel.

});

const buttonClass = computed(() => {
return [
'pkpButton',
props.isSecondary ? 'pkpButton--secondary' : 'pkpButton--primary',
props.isWarnable ? 'pkpButton--warnable' : '',
];
});
</script>
Expand Down Expand Up @@ -73,4 +76,17 @@ const buttonClass = computed(() => {
border-color: var(--pkp-text-color-disabled);
color: var(--pkp-text-color-disabled);
}

.pkpButton--warnable {
color: var(--pkp-color-negative);
border-color: var(--pkp-border-color-light);
background-color: var(--pkp-background-color-secondary);
}

.pkpButton--warnable:disabled {
color: var(--pkp-text-color-disabled);
}
.pkpButton--warnable:hover {
background-color: var(--pkp-background-color-secondary);
}
</style>
11 changes: 10 additions & 1 deletion src/frontend/components/PkpIcon/PkpIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@
import {computed} from 'vue';
import Cancel from './icons/Cancel.vue';
import MoreOptions from './icons/MoreOptions.vue';

import Error from './icons/Error.vue';
import Help from './icons/Help.vue';
import User from './icons/User.vue';
import Orcid from '@/frontend/components/PkpIcon/icons/Orcid.vue';
import OrcidUnauthenticated from '@/frontend/components/PkpIcon/icons/OrcidUnauthenticated.vue';
const svgIcons = {
Cancel,
MoreOptions,
Error,
Help,
User,
Orcid,
OrcidUnauthenticated,
};

const props = defineProps({
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/components/PkpIcon/icons/Error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.8849 3.4905C12.5389 2.8365 11.4629 2.8365 11.1169 3.4905L2.11694 20.4905C2.03592 20.6429 1.99575 20.8136 2.00036 20.9861C2.00496 21.1586 2.05417 21.327 2.14319 21.4749C2.23221 21.6227 2.35801 21.7449 2.50833 21.8297C2.65865 21.9145 2.82837 21.9588 3.00094 21.9585H21.0009C21.1734 21.9589 21.343 21.9145 21.4932 21.8298C21.6434 21.7451 21.7691 21.6229 21.8581 21.4752C21.947 21.3274 21.9961 21.1591 22.0007 20.9867C22.0052 20.8144 21.965 20.6437 21.8839 20.4915L12.8849 3.4905ZM13.0009 18.9585H11.0009V16.9585H13.0009V18.9585ZM11.0009 14.9585V9.9585H13.0009L13.0019 14.9585H11.0009Z"
fill="currentColor"
/>
</svg>
</template>
27 changes: 27 additions & 0 deletions src/frontend/components/PkpIcon/icons/Help.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3415_5808)">
<rect
x="1.5"
y="1.5"
width="15"
height="15"
rx="7.5"
stroke="currentColor"
/>
<path
d="M10.1556 6.49536C10.8447 6.49536 11.4033 5.93675 11.4033 5.24768C11.4033 4.5586 10.8447 4 10.1556 4C9.46656 4 8.90796 4.5586 8.90796 5.24768C8.90796 5.93675 9.46656 6.49536 10.1556 6.49536Z"
fill="currentColor"
/>
<path
d="M11.4315 11.5578C11.397 11.5346 11.3583 11.5185 11.3175 11.5105C11.2767 11.5025 11.2348 11.5027 11.1941 11.5111C11.1534 11.5196 11.1148 11.536 11.0806 11.5596C11.0464 11.5831 11.0172 11.6133 10.9948 11.6482C10.6932 12.0948 10.3239 12.4915 9.89995 12.8242C9.79702 12.9021 9.41335 13.1985 9.25116 13.1361C9.13887 13.1018 9.20437 12.8803 9.2262 12.7867L9.39152 12.297C9.46014 12.0974 10.6548 8.55399 10.7858 8.14849C10.9792 7.55585 10.895 6.97256 10.0122 7.11292C9.77206 7.13787 7.33597 7.45291 7.2923 7.45603C7.25134 7.45869 7.2113 7.4694 7.17448 7.48753C7.13765 7.50567 7.10476 7.53088 7.07768 7.56173C7.0506 7.59257 7.02986 7.62845 7.01664 7.66732C7.00343 7.70618 6.998 7.74726 7.00066 7.78823C7.00332 7.82919 7.01403 7.86922 7.03216 7.90605C7.0503 7.94287 7.07551 7.97577 7.10635 8.00285C7.1372 8.02993 7.17308 8.05067 7.21194 8.06389C7.25081 8.0771 7.29189 8.08253 7.33285 8.07987C7.33285 8.07987 8.26861 7.95822 8.37154 7.94886C8.42427 7.94372 8.47736 7.95394 8.5244 7.97829C8.57145 8.00264 8.61044 8.04009 8.63668 8.08611C8.69319 8.26082 8.68545 8.44998 8.61484 8.61949C8.53374 8.93141 7.25175 12.5434 7.2112 12.7524C7.16771 12.9273 7.17979 13.1114 7.24575 13.2791C7.31171 13.4468 7.42829 13.5898 7.57927 13.6882C7.86265 13.8771 8.20007 13.968 8.53998 13.9471C8.87048 13.943 9.19734 13.8775 9.50381 13.7537C10.2805 13.4418 11.0915 12.6121 11.5188 11.9601C11.5556 11.8939 11.567 11.8165 11.551 11.7425C11.5349 11.6685 11.4924 11.6028 11.4315 11.5578Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_3415_5808">
<rect width="18" height="18" fill="white" />
</clipPath>
</defs>
</svg>
</template>
19 changes: 19 additions & 0 deletions src/frontend/components/PkpIcon/icons/Orcid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_11_315)">
<path
d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24Z"
fill="#A6CE39"
/>
<path
d="M8.10938 17.4375H6.65625V7.40625H8.10938V17.4375ZM10.2188 7.40625H14.1094C17.8125 7.40625 19.4531 10.0781 19.4531 12.4219C19.4531 15 17.4375 17.4375 14.1094 17.4375H10.1719L10.2188 7.40625ZM11.6719 16.1719H13.9688C17.25 16.1719 18 13.6875 18 12.4688C18 10.4531 16.7344 8.76562 13.9219 8.76562H11.7188L11.6719 16.1719ZM8.34375 5.34375C8.34375 5.85938 7.92188 6.28125 7.40625 6.28125C6.89062 6.28125 6.46875 5.85938 6.46875 5.34375C6.46875 5.09511 6.56752 4.85665 6.74334 4.68084C6.91915 4.50502 7.15761 4.40625 7.40625 4.40625C7.92188 4.40625 8.34375 4.82812 8.34375 5.34375Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_11_315">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
</template>
27 changes: 27 additions & 0 deletions src/frontend/components/PkpIcon/icons/OrcidUnauthenticated.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<svg
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="mr-2 inline-block w-6 align-middle"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M29.474 16c0 7.442-6.032 13.474-13.474 13.474S2.526 23.442 2.526 16 8.558 2.526 16 2.526 29.474 8.558 29.474 16Z"
fill="#fff"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M32 16c0 8.837-7.163 16-16 16S0 24.837 0 16 7.163 0 16 0s16 7.163 16 16ZM16 29.474c7.442 0 13.474-6.032 13.474-13.474S23.442 2.526 16 2.526 2.526 8.558 2.526 16 8.558 29.474 16 29.474Z"
fill="#7FAA26"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.22 10.973h-4.547v11.6h4.569c3.8 0 6.133-2.82 6.133-5.8 0-1.365-.469-2.815-1.478-3.925-1.013-1.115-2.557-1.875-4.676-1.875Zm-.177 9.732h-2.347v-7.864h2.264c1.521 0 2.603.46 3.304 1.167.703.709 1.046 1.688 1.046 2.765 0 .654-.2 1.641-.83 2.46-.621.808-1.677 1.473-3.437 1.473Zm-.083-8.073c3.13 0 4.558 1.898 4.558 4.141 0-2.243-1.429-4.141-4.558-4.141h-2.472 2.472Zm6.205 4.017.001.124c0 2.869-2.242 5.591-5.924 5.591h-4.36V11.182v11.182h4.36c3.682 0 5.924-2.722 5.924-5.591l-.001-.124ZM9.5 11.005v11.588h2.024V11.005H9.5Zm1.815.208v11.172-11.172H9.71h1.606ZM10.512 10.15c.7 0 1.262-.575 1.262-1.263 0-.687-.561-1.262-1.262-1.262-.7 0-1.262.563-1.262 1.262 0 .688.561 1.262 1.262 1.262Zm.614-.408a1.044 1.044 0 0 0 0 0Z"
fill="#7FAA26"
/>
</svg>
</template>
10 changes: 10 additions & 0 deletions src/frontend/components/PkpIcon/icons/User.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5 21C5 17.134 8.13401 14 12 14C15.866 14 19 17.134 19 21M16 7C16 9.20914 14.2091 11 12 11C9.79086 11 8 9.20914 8 7C8 4.79086 9.79086 3 12 3C14.2091 3 16 4.79086 16 7Z"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
3 changes: 3 additions & 0 deletions src/frontend/components/PkpModal/PkpDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ import {
DialogTitle,
} from 'reka-ui';
import PkpIcon from '@/frontend/components/PkpIcon/PkpIcon.vue';
import {usePkpLocalize} from '@/frontend/composables/usePkpLocalize';

const {t} = usePkpLocalize();

const props = defineProps({
/** Used only internally, don't pass this prop via openDialog */
Expand Down
7 changes: 4 additions & 3 deletions src/frontend/components/PkpModal/PkpModalBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<div class="pkpModalBody__closeWrapper">
<DialogClose
ref="closeModalButton"
type="button"
as="button"
class="pkpModalBody__close"
>
<span class="pkpModalBody__srOnly">
Expand Down Expand Up @@ -78,8 +78,8 @@ import PkpIcon from '@/frontend/components/PkpIcon/PkpIcon.vue';
import {focusFirstHeading} from '@/components/Modal/modalHelpers';

const containerId = useId();
import {useLocalize} from '@/composables/useLocalize';
const {t} = useLocalize();
import {usePkpLocalize} from '@/frontend/composables/usePkpLocalize';
const {t} = usePkpLocalize();
const closeModal = inject('closeModal');
const closeModalButton = inject('closeModalButton');
/* Initial focus */
Expand Down Expand Up @@ -230,6 +230,7 @@ function handleAutoFocus(event) {
position: relative;
outline: none;
background-color: var(--pkp-background-color-secondary);
cursor: pointer;
}

.pkpModalBody__close:hover {
Expand Down
11 changes: 10 additions & 1 deletion src/frontend/components/PkpTextarea/PkpTextarea.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<template>
<div class="pkpTextarea">
<Label class="pkpTextarea__label" :for="textareaId">
<Label
class="pkpTextarea__label"
:class="{'-screenReader': isLabelSrOnly}"
:for="textareaId"
>
{{ props.label }}
</Label>
<textarea
Expand Down Expand Up @@ -35,6 +39,11 @@ const props = defineProps({
type: Boolean,
default: false,
},
// Whether the label should be visually hidden but still accessible to screen readers
isLabelSrOnly: {
type: Boolean,
default: false,
},
});

defineEmits(['update:modelValue']);
Expand Down
70 changes: 70 additions & 0 deletions src/frontend/components/PkpUserComment/PkpScrollToComments.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<div>
<a
id="scrollToComments"
style="cursor: pointer"
@click.prevent="scrollToComments"
>
{{ t('userComment.allComments', {commentCount: allCommentsCount}) }}
</a>
<div style="margin-top: 1.5rem">
<a
v-if="!currentUser"
class="pkpScrollToComments__login"
:href="loginUrl"
>
{{ t('userComment.login') }}
</a>
</div>
</div>
</template>

<script setup>
import {usePkpLocalize} from '@/frontend/composables/usePkpLocalize';

const {t} = usePkpLocalize();
const currentUser = pkp.currentUser;

defineProps({
/** The total number of approved comments across all publication versions */
allCommentsCount: {
type: Number,
required: true,
},
/** The URL to the login page */
loginUrl: {
type: String,
required: true,
},
});

const scrollToComments = () => {
// Element with ID 'pkpUserCommentsContainer' is defined in the pkpUserComment component.
const commentsContainer = document.getElementById('pkpUserCommentsContainer');
if (commentsContainer) {
commentsContainer.scrollIntoView({behavior: 'smooth'});
}
};
</script>

<style>
.pkpScrollToComments__login {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--pkp-spacing-2) var(--pkp-spacing-4);
font: var(--pkp-font-base-bold);
border-radius: var(--pkp-radius);
cursor: pointer;
border: 1px solid var(--pkp-color-transparent);
gap: var(--pkp-spacing-2);
background-color: var(--pkp-color-primary);
color: var(--pkp-text-color-on-dark);
text-decoration: none;
}

.pkpScrollToComments__login:hover {
background-color: var(--pkp-color-hover);
color: var(--pkp-text-color-on-dark);
}
</style>
95 changes: 95 additions & 0 deletions src/frontend/components/PkpUserComment/PkpUserComment.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div id="pkpUserCommentsContainer">
<h3>{{ t('userComment.commentOnThisPublication') }}</h3>

<template v-for="id in publicationIds" :key="id">
<PkpAccordion
:title="getVersionTitle(id)"
:default-open="id === latestPublicationId"
>
<PkpButton
v-if="!currentUser && id === latestPublicationId"
class="pkpUserComments__loginButton"
@click="login"
>
{{ t('userComment.login') }}
</PkpButton>

<PkpUserCommentsList
:publication-id="id"
:items-per-page="itemsPerPage"
:is-latest-publication="id === latestPublicationId"
:total-publication-comments="commentsCountPerPublication[id]"
/>
</PkpAccordion>
</template>
</div>
</template>

<script setup>
import PkpUserCommentsList from '@/frontend/components/PkpUserComment/PkpUserCommentsList.vue';
import PkpAccordion from '@/frontend/components/PkpAccordion/PkpAccordion.vue';
import PkpButton from '@/frontend/components/PkpButton/PkpButton.vue';
import {usePkpLocalize} from '@/frontend/composables/usePkpLocalize';

const {t} = usePkpLocalize();
const props = defineProps({
/** The ID of the latest publication associated with the published item(article, book, etc.)*/
latestPublicationId: {
type: Number,
required: true,
},
/** An array of published publication IDs associated with the published item(article, book, etc.) */
publicationIds: {
type: Array,
required: false,
default: () => [],
},
/** Number of comments get when fetching comments */
itemsPerPage: {
type: Number,
required: true,
},
/**
* URL to redirect the user to login page
*/
loginUrl: {
type: String,
required: true,
},
/**
* An object where keys are publication IDs and values are the number of approved comments for that publication
*/
commentsCountPerPublication: {
type: Object,
required: true,
},
});
const currentUser = pkp.currentUser;

/**
* Returns the title for the accordion based on the publication ID and the number of comments.
* @param {number} id - The publication ID.
* @returns {string} The title for the accordion.
*/
function getVersionTitle(id) {
return t('userComment.versionWithCount', {
version: id,
versionCommentsCount: props.commentsCountPerPublication[id] || 0,
});
}

/**
* Redirects the user to the login page.
*/
function login() {
window.location = props.loginUrl;
}
</script>

<style>
.pkpUserComments__loginButton {
margin-top: 0.5rem;
margin-bottom: 1rem;
}
</style>
Loading