diff --git a/packages/frontend-2/assets/css/tailwind.css b/packages/frontend-2/assets/css/tailwind.css index 0cef5722a..7b094ca40 100644 --- a/packages/frontend-2/assets/css/tailwind.css +++ b/packages/frontend-2/assets/css/tailwind.css @@ -24,3 +24,11 @@ body, div#__nuxt { height: 100%; } + +@layer components { + .terms-of-service { + a { + @apply underline; + } + } +} diff --git a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue index 126182828..97f079921 100644 --- a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue +++ b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue @@ -61,7 +61,7 @@ Sign up
@@ -164,8 +164,3 @@ watch( { immediate: true } ) - diff --git a/packages/frontend-2/components/common/tiptap/MentionListItem.vue b/packages/frontend-2/components/common/tiptap/MentionListItem.vue index c02460c32..679c813cb 100644 --- a/packages/frontend-2/components/common/tiptap/MentionListItem.vue +++ b/packages/frontend-2/components/common/tiptap/MentionListItem.vue @@ -3,7 +3,7 @@ diff --git a/packages/frontend-2/components/common/tiptap/TextEditor.vue b/packages/frontend-2/components/common/tiptap/TextEditor.vue index 9bbcf9ac6..506933c64 100644 --- a/packages/frontend-2/components/common/tiptap/TextEditor.vue +++ b/packages/frontend-2/components/common/tiptap/TextEditor.vue @@ -24,8 +24,9 @@ import type { TiptapEditorSchemaOptions } from '~~/lib/common/helpers/tiptap' import type { Nullable } from '@speckle/shared' -import { userProfileRoute } from '~~/lib/common/helpers/route' +// import { userProfileRoute } from '~~/lib/common/helpers/route' import { onKeyDown } from '@vueuse/core' +import { noop } from 'lodash-es' const emit = defineEmits<{ (e: 'update:modelValue', val: JSONContent): void @@ -95,17 +96,19 @@ const onEditorContentClick = (e: MouseEvent) => { e.stopPropagation() } -const onMentionClick = (userId: string, e: MouseEvent) => { - if (!props.readonly) return +// TODO: No profile page to link to in FE2 yet +// const onMentionClick = (userId: string, e: MouseEvent) => { +// if (!props.readonly) return - const path = userProfileRoute(userId) - const isMetaKey = e.metaKey || e.ctrlKey - if (isMetaKey) { - window.open(path, '_blank') - } else { - window.location.href = path - } -} +// const path = userProfileRoute(userId) +// const isMetaKey = e.metaKey || e.ctrlKey +// if (isMetaKey) { +// window.open(path, '_blank') +// } else { +// window.location.href = path +// } +// } +const onMentionClick = noop onKeyDown( 'Escape', @@ -184,7 +187,7 @@ onBeforeUnmount(() => { box-shadow: unset !important; .editor-mention { - cursor: pointer; + /* cursor: pointer; TODO: Reenable once mentions are clickable again */ } } } diff --git a/packages/frontend-2/components/developer-settings/DeleteDialog.vue b/packages/frontend-2/components/developer-settings/DeleteDialog.vue index 072d47653..1be48edae 100644 --- a/packages/frontend-2/components/developer-settings/DeleteDialog.vue +++ b/packages/frontend-2/components/developer-settings/DeleteDialog.vue @@ -1,18 +1,21 @@ diff --git a/packages/frontend-2/components/developer-settings/SectionHeader.vue b/packages/frontend-2/components/developer-settings/SectionHeader.vue index aee03df8b..4b15fae01 100644 --- a/packages/frontend-2/components/developer-settings/SectionHeader.vue +++ b/packages/frontend-2/components/developer-settings/SectionHeader.vue @@ -1,7 +1,8 @@ diff --git a/packages/frontend-2/composables/globals.ts b/packages/frontend-2/composables/globals.ts new file mode 100644 index 000000000..c331c0088 --- /dev/null +++ b/packages/frontend-2/composables/globals.ts @@ -0,0 +1,4 @@ +import { useActiveUser } from '~/lib/auth/composables/activeUser' +import { useGlobalToast } from '~/lib/common/composables/toast' + +export { useGlobalToast, useActiveUser } diff --git a/packages/frontend-2/layouts/loginOrRegister.vue b/packages/frontend-2/layouts/loginOrRegister.vue index 0ffe2712d..f9de80d99 100644 --- a/packages/frontend-2/layouts/loginOrRegister.vue +++ b/packages/frontend-2/layouts/loginOrRegister.vue @@ -1,28 +1,19 @@ diff --git a/packages/frontend-2/lib/auth/composables/activeUser.ts b/packages/frontend-2/lib/auth/composables/activeUser.ts index 849ca9751..e98f96a17 100644 --- a/packages/frontend-2/lib/auth/composables/activeUser.ts +++ b/packages/frontend-2/lib/auth/composables/activeUser.ts @@ -57,11 +57,21 @@ export function useActiveUser() { const user = activeUser.value return getDistinctId(user) }) + const userId = computed(() => activeUser.value?.id) const isGuest = computed(() => activeUser.value?.role === Roles.Server.Guest) const isAdmin = computed(() => activeUser.value?.role === Roles.Server.Admin) - return { activeUser, isLoggedIn, distinctId, refetch, onResult, isGuest, isAdmin } + return { + activeUser, + userId, + isLoggedIn, + distinctId, + refetch, + onResult, + isGuest, + isAdmin + } } /** diff --git a/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts b/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts index 8b1819655..ee372cc1c 100644 --- a/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts +++ b/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts @@ -1,3 +1,4 @@ +import type { RouteLocationNormalized } from '#vue-router' import type { Optional } from '@speckle/shared' import { reduce } from 'lodash-es' import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie' @@ -8,10 +9,12 @@ const usePostAuthRedirectCookie = () => maxAge: 60 * 5 // 5 mins }) -export const usePostAuthRedirect = () => { +export const usePostAuthRedirect = ( + options?: Partial<{ route: RouteLocationNormalized }> +) => { const cookie = usePostAuthRedirectCookie() const router = useRouter() - const route = useRoute() + const route = options?.route || useRoute() const logger = useLogger() const hadPendingRedirect = computed(() => !!cookie.value?.length) diff --git a/packages/frontend-2/lib/common/composables/toast.ts b/packages/frontend-2/lib/common/composables/toast.ts index a5b7ee997..e52d37666 100644 --- a/packages/frontend-2/lib/common/composables/toast.ts +++ b/packages/frontend-2/lib/common/composables/toast.ts @@ -38,7 +38,7 @@ export function useGlobalToastManager() { // (re-)init timeout stop() - start() + if (newVal.autoClose !== false) start() }) }, { deep: true } diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index b7c58cfdf..8dc4422d8 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -86,8 +86,10 @@ const documents = { "\n mutation DeleteApplication($appId: String!) {\n appDelete(appId: $appId)\n }\n": types.DeleteApplicationDocument, "\n mutation CreateApplication($app: AppCreateInput!) {\n appCreate(app: $app)\n }\n": types.CreateApplicationDocument, "\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n": types.EditApplicationDocument, + "\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n": types.RevokeAppAccessDocument, "\n query DeveloperSettingsAccessTokens {\n activeUser {\n id\n apiTokens {\n id\n name\n lastUsed\n lastChars\n createdAt\n scopes\n }\n }\n }\n": types.DeveloperSettingsAccessTokensDocument, "\n query DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\n }\n }\n": types.DeveloperSettingsApplicationsDocument, + "\n query DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\n }\n": types.DeveloperSettingsAuthorizedAppsDocument, "\n query SearchProjects($search: String, $onlyWithRoles: [String!] = null) {\n activeUser {\n projects(limit: 10, filter: { search: $search, onlyWithRoles: $onlyWithRoles }) {\n totalCount\n items {\n ...FormSelectProjects_Project\n }\n }\n }\n }\n": types.SearchProjectsDocument, "\n fragment ProjectDashboardItemNoModels on Project {\n id\n name\n createdAt\n updatedAt\n role\n team {\n user {\n id\n name\n avatar\n }\n }\n ...ProjectPageModelsCardProject\n }\n": types.ProjectDashboardItemNoModelsFragmentDoc, "\n fragment ProjectDashboardItem on Project {\n id\n ...ProjectDashboardItemNoModels\n models(limit: 4, filter: { onlyWithVersions: true }) {\n totalCount\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n pendingImportedModels(limit: 4) {\n ...PendingFileUpload\n }\n }\n": types.ProjectDashboardItemFragmentDoc, @@ -481,6 +483,10 @@ export function graphql(source: "\n mutation CreateApplication($app: AppCreateI * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n"): (typeof documents)["\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n"): (typeof documents)["\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -489,6 +495,10 @@ export function graphql(source: "\n query DeveloperSettingsAccessTokens {\n * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\n }\n }\n"): (typeof documents)["\n query DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\n }\n"): (typeof documents)["\n query DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 51e92e93e..fe8e9704e 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -2603,7 +2603,7 @@ export type User = { /** Returns a list of your personal api tokens. */ apiTokens: Array; /** Returns the apps you have authorized. */ - authorizedApps?: Maybe>>; + authorizedApps?: Maybe>; avatar?: Maybe; bio?: Maybe; /** @@ -3171,6 +3171,13 @@ export type EditApplicationMutationVariables = Exact<{ export type EditApplicationMutation = { __typename?: 'Mutation', appUpdate: boolean }; +export type RevokeAppAccessMutationVariables = Exact<{ + appId: Scalars['String']; +}>; + + +export type RevokeAppAccessMutation = { __typename?: 'Mutation', appRevokeAccess?: boolean | null }; + export type DeveloperSettingsAccessTokensQueryVariables = Exact<{ [key: string]: never; }>; @@ -3181,6 +3188,11 @@ export type DeveloperSettingsApplicationsQueryVariables = Exact<{ [key: string]: export type DeveloperSettingsApplicationsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, createdApps?: Array<{ __typename?: 'ServerApp', id: string, secret?: string | null, name: string, description?: string | null, redirectUrl: string, scopes: Array<{ __typename?: 'Scope', name: string, description: string }> }> | null } | null }; +export type DeveloperSettingsAuthorizedAppsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type DeveloperSettingsAuthorizedAppsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, authorizedApps?: Array<{ __typename?: 'ServerAppListItem', id: string, description?: string | null, name: string, author?: { __typename?: 'AppAuthor', id: string, name: string, avatar?: string | null } | null }> | null } | null }; + export type SearchProjectsQueryVariables = Exact<{ search?: InputMaybe; onlyWithRoles?: InputMaybe | Scalars['String']>; @@ -3838,8 +3850,10 @@ export const CreateAccessTokenDocument = {"kind":"Document","definitions":[{"kin export const DeleteApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}]}]}}]} as unknown as DocumentNode; export const CreateApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"app"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AppCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"app"},"value":{"kind":"Variable","name":{"kind":"Name","value":"app"}}}]}]}}]} as unknown as DocumentNode; export const EditApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"app"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AppUpdateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appUpdate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"app"},"value":{"kind":"Variable","name":{"kind":"Name","value":"app"}}}]}]}}]} as unknown as DocumentNode; +export const RevokeAppAccessDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RevokeAppAccess"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appRevokeAccess"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}]}]}}]} as unknown as DocumentNode; export const DeveloperSettingsAccessTokensDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeveloperSettingsAccessTokens"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"apiTokens"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lastUsed"}},{"kind":"Field","name":{"kind":"Name","value":"lastChars"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}}]}}]}}]}}]} as unknown as DocumentNode; export const DeveloperSettingsApplicationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeveloperSettingsApplications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUrl"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; +export const DeveloperSettingsAuthorizedAppsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeveloperSettingsAuthorizedApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorizedApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const SearchProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SearchProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"onlyWithRoles"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"onlyWithRoles"},"value":{"kind":"Variable","name":{"kind":"Name","value":"onlyWithRoles"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormSelectProjects_Project"}}]}}]}}]}}]}},...FormSelectProjects_ProjectFragmentDoc.definitions]} as unknown as DocumentNode; export const CreateModelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateModel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateModelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}}]}},...ProjectPageLatestItemsModelItemFragmentDoc.definitions,...PendingFileUploadFragmentDoc.definitions,...ProjectPageModelsCardRenameDialogFragmentDoc.definitions,...ProjectPageModelsCardDeleteDialogFragmentDoc.definitions,...ProjectPageModelsActionsFragmentDoc.definitions,...ModelCardAutomationStatus_ModelFragmentDoc.definitions,...ModelCardAutomationStatus_AutomationsStatusFragmentDoc.definitions]} as unknown as DocumentNode; export const CreateProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCreateInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageProject"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}}]}}]}},...ProjectPageProjectFragmentDoc.definitions,...ProjectPageProjectHeaderFragmentDoc.definitions,...ProjectPageStatsBlockTeamFragmentDoc.definitions,...LimitedUserAvatarFragmentDoc.definitions,...ProjectPageTeamDialogFragmentDoc.definitions,...ProjectsPageTeamDialogManagePermissions_ProjectFragmentDoc.definitions,...ProjectPageStatsBlockVersionsFragmentDoc.definitions,...ProjectPageStatsBlockModelsFragmentDoc.definitions,...ProjectPageStatsBlockCommentsFragmentDoc.definitions,...ProjectPageLatestItemsModelsFragmentDoc.definitions,...ProjectPageModelsStructureItem_ProjectFragmentDoc.definitions,...ProjectPageModelsActions_ProjectFragmentDoc.definitions,...ProjectsModelPageEmbed_ProjectFragmentDoc.definitions,...ProjectPageLatestItemsCommentsFragmentDoc.definitions,...ProjectDashboardItemFragmentDoc.definitions,...ProjectDashboardItemNoModelsFragmentDoc.definitions,...ProjectPageModelsCardProjectFragmentDoc.definitions,...ProjectPageLatestItemsModelItemFragmentDoc.definitions,...PendingFileUploadFragmentDoc.definitions,...ProjectPageModelsCardRenameDialogFragmentDoc.definitions,...ProjectPageModelsCardDeleteDialogFragmentDoc.definitions,...ProjectPageModelsActionsFragmentDoc.definitions,...ModelCardAutomationStatus_ModelFragmentDoc.definitions,...ModelCardAutomationStatus_AutomationsStatusFragmentDoc.definitions]} as unknown as DocumentNode; diff --git a/packages/frontend-2/lib/common/helpers/md5.js b/packages/frontend-2/lib/common/helpers/md5.js deleted file mode 100644 index 75d2be9a4..000000000 --- a/packages/frontend-2/lib/common/helpers/md5.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Lightweight MD5 implementation. - * @see http://www.myersdaily.org/joseph/javascript/md5-text.html - */ - -function md5cycle(x, k) { - let a = x[0], - b = x[1], - c = x[2], - d = x[3] - - a = ff(a, b, c, d, k[0], 7, -680876936) - d = ff(d, a, b, c, k[1], 12, -389564586) - c = ff(c, d, a, b, k[2], 17, 606105819) - b = ff(b, c, d, a, k[3], 22, -1044525330) - a = ff(a, b, c, d, k[4], 7, -176418897) - d = ff(d, a, b, c, k[5], 12, 1200080426) - c = ff(c, d, a, b, k[6], 17, -1473231341) - b = ff(b, c, d, a, k[7], 22, -45705983) - a = ff(a, b, c, d, k[8], 7, 1770035416) - d = ff(d, a, b, c, k[9], 12, -1958414417) - c = ff(c, d, a, b, k[10], 17, -42063) - b = ff(b, c, d, a, k[11], 22, -1990404162) - a = ff(a, b, c, d, k[12], 7, 1804603682) - d = ff(d, a, b, c, k[13], 12, -40341101) - c = ff(c, d, a, b, k[14], 17, -1502002290) - b = ff(b, c, d, a, k[15], 22, 1236535329) - - a = gg(a, b, c, d, k[1], 5, -165796510) - d = gg(d, a, b, c, k[6], 9, -1069501632) - c = gg(c, d, a, b, k[11], 14, 643717713) - b = gg(b, c, d, a, k[0], 20, -373897302) - a = gg(a, b, c, d, k[5], 5, -701558691) - d = gg(d, a, b, c, k[10], 9, 38016083) - c = gg(c, d, a, b, k[15], 14, -660478335) - b = gg(b, c, d, a, k[4], 20, -405537848) - a = gg(a, b, c, d, k[9], 5, 568446438) - d = gg(d, a, b, c, k[14], 9, -1019803690) - c = gg(c, d, a, b, k[3], 14, -187363961) - b = gg(b, c, d, a, k[8], 20, 1163531501) - a = gg(a, b, c, d, k[13], 5, -1444681467) - d = gg(d, a, b, c, k[2], 9, -51403784) - c = gg(c, d, a, b, k[7], 14, 1735328473) - b = gg(b, c, d, a, k[12], 20, -1926607734) - - a = hh(a, b, c, d, k[5], 4, -378558) - d = hh(d, a, b, c, k[8], 11, -2022574463) - c = hh(c, d, a, b, k[11], 16, 1839030562) - b = hh(b, c, d, a, k[14], 23, -35309556) - a = hh(a, b, c, d, k[1], 4, -1530992060) - d = hh(d, a, b, c, k[4], 11, 1272893353) - c = hh(c, d, a, b, k[7], 16, -155497632) - b = hh(b, c, d, a, k[10], 23, -1094730640) - a = hh(a, b, c, d, k[13], 4, 681279174) - d = hh(d, a, b, c, k[0], 11, -358537222) - c = hh(c, d, a, b, k[3], 16, -722521979) - b = hh(b, c, d, a, k[6], 23, 76029189) - a = hh(a, b, c, d, k[9], 4, -640364487) - d = hh(d, a, b, c, k[12], 11, -421815835) - c = hh(c, d, a, b, k[15], 16, 530742520) - b = hh(b, c, d, a, k[2], 23, -995338651) - - a = ii(a, b, c, d, k[0], 6, -198630844) - d = ii(d, a, b, c, k[7], 10, 1126891415) - c = ii(c, d, a, b, k[14], 15, -1416354905) - b = ii(b, c, d, a, k[5], 21, -57434055) - a = ii(a, b, c, d, k[12], 6, 1700485571) - d = ii(d, a, b, c, k[3], 10, -1894986606) - c = ii(c, d, a, b, k[10], 15, -1051523) - b = ii(b, c, d, a, k[1], 21, -2054922799) - a = ii(a, b, c, d, k[8], 6, 1873313359) - d = ii(d, a, b, c, k[15], 10, -30611744) - c = ii(c, d, a, b, k[6], 15, -1560198380) - b = ii(b, c, d, a, k[13], 21, 1309151649) - a = ii(a, b, c, d, k[4], 6, -145523070) - d = ii(d, a, b, c, k[11], 10, -1120210379) - c = ii(c, d, a, b, k[2], 15, 718787259) - b = ii(b, c, d, a, k[9], 21, -343485551) - - x[0] = add32(a, x[0]) - x[1] = add32(b, x[1]) - x[2] = add32(c, x[2]) - x[3] = add32(d, x[3]) -} - -function cmn(q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)) - return add32((a << s) | (a >>> (32 - s)), b) -} - -function ff(a, b, c, d, x, s, t) { - return cmn((b & c) | (~b & d), a, b, x, s, t) -} - -function gg(a, b, c, d, x, s, t) { - return cmn((b & d) | (c & ~d), a, b, x, s, t) -} - -function hh(a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t) -} - -function ii(a, b, c, d, x, s, t) { - return cmn(c ^ (b | ~d), a, b, x, s, t) -} - -function md51(s) { - const n = s.length, - state = [1732584193, -271733879, -1732584194, 271733878] - - let i - for (i = 64; i <= s.length; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))) - } - s = s.substring(i - 64) - const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3) - tail[i >> 2] |= 0x80 << (i % 4 << 3) - if (i > 55) { - md5cycle(state, tail) - for (i = 0; i < 16; i++) tail[i] = 0 - } - tail[14] = n * 8 - md5cycle(state, tail) - return state -} - -function md5blk(s) { - /* I figured global was faster. */ - const md5blks = [] - let i - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = - s.charCodeAt(i) + - (s.charCodeAt(i + 1) << 8) + - (s.charCodeAt(i + 2) << 16) + - (s.charCodeAt(i + 3) << 24) - } - return md5blks -} - -const HEX_CHR = '0123456789abcdef'.split('') - -function rhex(n) { - let s = '', - j = 0 - for (; j < 4; j++) - s += HEX_CHR[(n >> (j * 8 + 4)) & 0x0f] + HEX_CHR[(n >> (j * 8)) & 0x0f] - return s -} - -function hex(x) { - for (let i = 0; i < x.length; i++) x[i] = rhex(x[i]) - return x.join('') -} - -let add32 = (a, b) => { - return (a + b) & 0xffffffff -} - -/** - * Generate an MD5 hash for an input string - * @param {string} s input string - * @returns {string} md5 hash - */ -function md5(s) { - return hex(md51(s)) -} - -if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { - add32 = (x, y) => { - const lsw = (x & 0xffff) + (y & 0xffff), - msw = (x >> 16) + (y >> 16) + (lsw >> 16) - return (msw << 16) | (lsw & 0xffff) - } -} - -export default md5 -export { md5 } diff --git a/packages/frontend-2/lib/common/helpers/md5.ts b/packages/frontend-2/lib/common/helpers/md5.ts new file mode 100644 index 000000000..10727f9a6 --- /dev/null +++ b/packages/frontend-2/lib/common/helpers/md5.ts @@ -0,0 +1,4 @@ +import { md5 } from '@speckle/shared' + +export default md5 +export { md5 } diff --git a/packages/frontend-2/lib/common/helpers/resources.ts b/packages/frontend-2/lib/common/helpers/resources.ts index eec12357c..a4c584b1f 100644 --- a/packages/frontend-2/lib/common/helpers/resources.ts +++ b/packages/frontend-2/lib/common/helpers/resources.ts @@ -1,6 +1,34 @@ +const streamRewriteRgx = /stream(?=$|s|\W)/gi +const branchRewriteRgx = /branch(es)?(?=$|\W)/gi +const commitRewriteRgx = /commit(?=$|s|\W)/gi + export function isObjectId(id: string) { return id.length === 32 } export const buildModelTreeItemId = (projectId: string, fullName: string) => `${projectId}-${fullName}` + +/** + * Converts old terminology (streams, branches, commits, etc.) to new one (projects, models, versions) + */ +export const toNewProductTerminology = (str: string): string => { + const isFirstCharUppercase = (val: string): boolean => + val?.length ? val[0] === val[0].toUpperCase() : false + + return str + .replaceAll(streamRewriteRgx, (match) => + isFirstCharUppercase(match) ? 'Project' : 'project' + ) + .replaceAll(branchRewriteRgx, (match) => { + const shouldBeUppercase = isFirstCharUppercase(match) + if (match === 'branches') { + return shouldBeUppercase ? 'Models' : 'models' + } else { + return shouldBeUppercase ? 'Model' : 'model' + } + }) + .replaceAll(commitRewriteRgx, (match) => + isFirstCharUppercase(match) ? 'Version' : 'version' + ) +} diff --git a/packages/frontend-2/lib/common/helpers/route.ts b/packages/frontend-2/lib/common/helpers/route.ts index 02bd3c80a..f6032dab2 100644 --- a/packages/frontend-2/lib/common/helpers/route.ts +++ b/packages/frontend-2/lib/common/helpers/route.ts @@ -35,11 +35,6 @@ export const automationDataPageRoute = (baseUrl: string, automationId: string) = export const threadRedirectRoute = (projectId: string, threadId: string) => `/projects/${projectId}/threads/${threadId}` -/** - * TODO: Page doesn't exist - */ -export const userProfileRoute = (userId: string) => `/profile/${userId}` - const buildNavigationComposable = (route: string) => () => { const router = useRouter() return (params?: { query?: LocationQueryRaw }) => { diff --git a/packages/frontend-2/lib/core/composables/theme.ts b/packages/frontend-2/lib/core/composables/theme.ts index b2b586ae6..f934ba8cb 100644 --- a/packages/frontend-2/lib/core/composables/theme.ts +++ b/packages/frontend-2/lib/core/composables/theme.ts @@ -15,10 +15,21 @@ export function useTheme() { const isDarkTheme = computed(() => themeCookie.value === AppTheme.Dark) const isLightTheme = computed(() => !isDarkTheme.value) + const setTheme = (newTheme: AppTheme) => { + themeCookie.value = newTheme + } + + const toggleTheme = () => { + if (isDarkTheme.value) { + setTheme(AppTheme.Light) + } else { + setTheme(AppTheme.Dark) + } + } + return { - setTheme: (newTheme: AppTheme) => { - themeCookie.value = newTheme - }, + setTheme, + toggleTheme, isDarkTheme, isLightTheme } diff --git a/packages/frontend-2/lib/developer-settings/graphql/mutations.ts b/packages/frontend-2/lib/developer-settings/graphql/mutations.ts index 3d1508abf..b8d109120 100644 --- a/packages/frontend-2/lib/developer-settings/graphql/mutations.ts +++ b/packages/frontend-2/lib/developer-settings/graphql/mutations.ts @@ -29,3 +29,9 @@ export const editApplicationMutation = graphql(` appUpdate(app: $app) } `) + +export const revokeAppAccessMutation = graphql(` + mutation RevokeAppAccess($appId: String!) { + appRevokeAccess(appId: $appId) + } +`) diff --git a/packages/frontend-2/lib/developer-settings/graphql/queries.ts b/packages/frontend-2/lib/developer-settings/graphql/queries.ts index bdb679a9e..7ea949e6f 100644 --- a/packages/frontend-2/lib/developer-settings/graphql/queries.ts +++ b/packages/frontend-2/lib/developer-settings/graphql/queries.ts @@ -34,3 +34,21 @@ export const developerSettingsApplicationsQuery = graphql(` } } `) + +export const developerSettingsAuthorizedAppsQuery = graphql(` + query DeveloperSettingsAuthorizedApps { + activeUser { + id + authorizedApps { + id + description + name + author { + id + name + avatar + } + } + } + } +`) diff --git a/packages/frontend-2/lib/developer-settings/helpers/types.ts b/packages/frontend-2/lib/developer-settings/helpers/types.ts index 648598179..11155e8d4 100644 --- a/packages/frontend-2/lib/developer-settings/helpers/types.ts +++ b/packages/frontend-2/lib/developer-settings/helpers/types.ts @@ -2,7 +2,8 @@ import { AllScopes } from '@speckle/shared' import type { Get } from 'type-fest' import type { DeveloperSettingsAccessTokensQuery, - DeveloperSettingsApplicationsQuery + DeveloperSettingsApplicationsQuery, + DeveloperSettingsAuthorizedAppsQuery } from '~~/lib/common/generated/gql/graphql' export type TokenItem = NonNullable< @@ -18,6 +19,10 @@ export type ApplicationItem = NonNullable< Get > +export type AuthorizedAppItem = NonNullable< + Get +> + export type ApplicationFormValues = { name: string scopes: Array<{ id: (typeof AllScopes)[number]; text: string }> diff --git a/packages/frontend-2/middleware/auth.ts b/packages/frontend-2/middleware/auth.ts index d91f463a1..063b7d11c 100644 --- a/packages/frontend-2/middleware/auth.ts +++ b/packages/frontend-2/middleware/auth.ts @@ -10,7 +10,7 @@ import { loginRoute } from '~~/lib/common/helpers/route' export default defineNuxtRouteMiddleware(async (to) => { const nuxt = useNuxtApp() const client = useApolloClientFromNuxt() - const postAuthRedirect = usePostAuthRedirect() + const postAuthRedirect = usePostAuthRedirect({ route: to }) const { data } = await client .query({ diff --git a/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue b/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue index 5349c8b34..bfc03cf0b 100644 --- a/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue +++ b/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue @@ -1,101 +1,184 @@ + diff --git a/packages/frontend-2/pages/developer-settings.vue b/packages/frontend-2/pages/developer-settings.vue index a75aabc02..33bc7e65c 100644 --- a/packages/frontend-2/pages/developer-settings.vue +++ b/packages/frontend-2/pages/developer-settings.vue @@ -26,6 +26,7 @@
@@ -104,6 +105,7 @@
@@ -184,6 +186,64 @@
+ +
+ + Here you can review the apps that you have granted access to. If something + looks suspicious, revoke the access. + + + + + + +
(null) +const itemToModify = ref(null) const tokenSuccess = ref('') const showCreateTokenDialog = ref(false) const showCreateTokenSuccessDialog = ref(false) @@ -262,7 +330,13 @@ const applications = computed(() => { return applicationsResult.value?.activeUser?.createdApps || [] }) -const openDeleteDialog = (item: TokenItem | ApplicationItem) => { +const authorizedApps = computed(() => + (authorizedAppsResult.value?.activeUser?.authorizedApps || []).filter( + (app) => app.id !== 'spklwebapp' + ) +) + +const openDeleteDialog = (item: TokenItem | ApplicationItem | AuthorizedAppItem) => { itemToModify.value = item showDeleteDialog.value = true } diff --git a/packages/frontend-2/utils/globals.ts b/packages/frontend-2/utils/globals.ts new file mode 100644 index 000000000..7959be854 --- /dev/null +++ b/packages/frontend-2/utils/globals.ts @@ -0,0 +1,3 @@ +import { ToastNotificationType } from '~~/lib/common/composables/toast' + +export { ToastNotificationType } diff --git a/packages/server/assets/auth/typedefs/apps.graphql b/packages/server/assets/auth/typedefs/apps.graphql index fd421930f..05258a8b7 100644 --- a/packages/server/assets/auth/typedefs/apps.graphql +++ b/packages/server/assets/auth/typedefs/apps.graphql @@ -46,7 +46,7 @@ extend type User { """ Returns the apps you have authorized. """ - authorizedApps: [ServerAppListItem] + authorizedApps: [ServerAppListItem!] @hasServerRole(role: SERVER_GUEST) @hasScope(scope: "apps:read") diff --git a/packages/server/modules/auth/rest/index.js b/packages/server/modules/auth/rest/index.js index bacfbc35b..f8759fbcc 100644 --- a/packages/server/modules/auth/rest/index.js +++ b/packages/server/modules/auth/rest/index.js @@ -24,6 +24,7 @@ module.exports = (app) => { */ app.get('/auth/accesscode', async (req, res) => { try { + const preventRedirect = !!req.query.preventRedirect const appId = req.query.appId const app = await getApp({ id: appId }) @@ -42,7 +43,11 @@ module.exports = (app) => { await validateScopes(scopes, Scopes.Tokens.Write) const ac = await createAuthorizationCode({ appId, userId, challenge }) - return res.redirect(`${app.redirectUrl}?access_code=${ac}`) + + const redirectUrl = `${app.redirectUrl}?access_code=${ac}` + return preventRedirect + ? res.status(200).json({ redirectUrl }) + : res.redirect(redirectUrl) } catch (err) { if ( err instanceof InvalidAccessCodeRequestError || diff --git a/packages/server/modules/auth/scopes.js b/packages/server/modules/auth/scopes.js index 0131b37de..680540e0f 100644 --- a/packages/server/modules/auth/scopes.js +++ b/packages/server/modules/auth/scopes.js @@ -5,12 +5,12 @@ const { Scopes } = require('@speckle/shared') module.exports = [ { name: Scopes.Apps.Read, - description: 'See what applications you have created or have authorized.', + description: 'See created or authorized applications.', public: false }, { name: Scopes.Apps.Write, - description: 'Register applications on your behalf.', + description: 'Register new applications.', public: false } ] diff --git a/packages/server/modules/automations/index.ts b/packages/server/modules/automations/index.ts index f6ecb7687..f0b21eba5 100644 --- a/packages/server/modules/automations/index.ts +++ b/packages/server/modules/automations/index.ts @@ -8,7 +8,7 @@ async function initScopes() { const scopes: ScopeRecord[] = [ { name: Scopes.Automate.ReportResults, - description: 'Allows the app to report automation results to the server.', + description: 'Report automation results to the server.', public: true } ] diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index 5fdcbb1fd..f0b116196 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -2616,7 +2616,7 @@ export type User = { /** Returns a list of your personal api tokens. */ apiTokens: Array; /** Returns the apps you have authorized. */ - authorizedApps?: Maybe>>; + authorizedApps?: Maybe>; avatar?: Maybe; bio?: Maybe; /** @@ -4242,7 +4242,7 @@ export type TokenResourceIdentifierResolvers = { activity?: Resolver, ParentType, ContextType, RequireFields>; apiTokens?: Resolver, ParentType, ContextType>; - authorizedApps?: Resolver>>, ParentType, ContextType>; + authorizedApps?: Resolver>, ParentType, ContextType>; avatar?: Resolver, ParentType, ContextType>; bio?: Resolver, ParentType, ContextType>; commits?: Resolver, ParentType, ContextType, RequireFields>; diff --git a/packages/server/modules/core/scopes.js b/packages/server/modules/core/scopes.js index f9d5e9a81..df421ca20 100644 --- a/packages/server/modules/core/scopes.js +++ b/packages/server/modules/core/scopes.js @@ -16,49 +16,49 @@ module.exports = [ }, { name: Scopes.Profile.Read, - description: 'Read your profile information (name, bio, company).', + description: 'Read your profile information.', public: true }, { name: Scopes.Profile.Email, - description: 'Grants access to the email address you registered with.', + description: 'Read the email address you registered with.', public: true }, { name: Scopes.Profile.Delete, - description: 'Allows a user to delete their account, with all associated data.', + description: 'Delete the account with all associated data.', public: false }, { name: Scopes.Users.Read, - description: "Read other users' profile on your behalf.", + description: "Read other users' profiles.", public: true }, { name: Scopes.Server.Stats, description: - 'Request server stats from the api. Only works in conjunction with a "server:admin" role.', + 'Request server stats from the API. Only works in conjunction with a "server:admin" role.', public: true }, { name: Scopes.Users.Email, - description: 'Access the emails of other users on your behalf.', + description: 'Access the emails of other users.', public: false }, { name: Scopes.Server.Setup, description: - 'Edit server information. Note: only server admins will be able to use this token.', + 'Edit server information. Note: Only server admins will be able to use this token.', public: false }, { name: Scopes.Tokens.Read, - description: 'Access your api tokens.', + description: 'Access API tokens.', public: false }, { name: Scopes.Tokens.Write, - description: 'Create and delete api tokens on your behalf.', + description: 'Create and delete API tokens.', public: false } ] diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index 1aae6aabc..1ce1540a4 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -2606,7 +2606,7 @@ export type User = { /** Returns a list of your personal api tokens. */ apiTokens: Array; /** Returns the apps you have authorized. */ - authorizedApps?: Maybe>>; + authorizedApps?: Maybe>; avatar?: Maybe; bio?: Maybe; /** diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 80348ee08..988ebba58 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -2607,7 +2607,7 @@ export type User = { /** Returns a list of your personal api tokens. */ apiTokens: Array; /** Returns the apps you have authorized. */ - authorizedApps?: Maybe>>; + authorizedApps?: Maybe>; avatar?: Maybe; bio?: Maybe; /** diff --git a/packages/ui-components/postcss.config.js b/packages/ui-components/postcss.config.js index a9b0b9302..3fe6e863a 100644 --- a/packages/ui-components/postcss.config.js +++ b/packages/ui-components/postcss.config.js @@ -1,7 +1,7 @@ export default { plugins: { + 'tailwindcss/nesting': {}, tailwindcss: {}, - autoprefixer: {}, - 'postcss-nesting': {} + autoprefixer: {} } } diff --git a/packages/ui-components/src/assets/setup/mentions.css b/packages/ui-components/src/assets/setup/mentions.css new file mode 100644 index 000000000..d20f05c33 --- /dev/null +++ b/packages/ui-components/src/assets/setup/mentions.css @@ -0,0 +1,15 @@ +/** + * Tippy mentions theme + */ +.tippy-box[data-theme='mention'] { + background: none; + pointer-events: none; + + .tippy-arrow { + display: none; + } + + .tippy-content > * { + pointer-events: auto; + } +} diff --git a/packages/ui-components/src/components/common/text/Link.vue b/packages/ui-components/src/components/common/text/Link.vue index 5de7e9ab7..1782c7f9a 100644 --- a/packages/ui-components/src/components/common/text/Link.vue +++ b/packages/ui-components/src/components/common/text/Link.vue @@ -1,6 +1,7 @@ -
+
@@ -55,8 +56,10 @@