Просмотр исходного кода

Merge branch 'master' of https://github.com/weseek/growi into feat/97283-create-activit-by-general-user-operation

Shun Miyazawa 3 лет назад
Родитель
Сommit
049fea9216

+ 1 - 3
packages/app/src/client/services/ContextExtractor.tsx

@@ -13,7 +13,7 @@ import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websoc
 import {
   useSiteUrl,
   useCurrentCreatedAt, useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
-  useIsDeleted, useIsNotCreatable, useIsTrashPage, useIsUserPage, useLastUpdateUsername,
+  useIsNotCreatable, useIsTrashPage, useIsUserPage, useLastUpdateUsername,
   useCurrentPageId, usePageIdOnHackmd, usePageUser, useCurrentPagePath, useRevisionCreatedAt, useRevisionId, useRevisionIdHackmdSynced,
   useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUpdatedAt, useCreator, useRevisionAuthor, useCurrentUser, useTargetAndAncestors,
   useNotFoundTargetPathOrId, useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
@@ -75,7 +75,6 @@ const ContextExtractorOnce: FC = () => {
   const isIdenticalPath = JSON.parse(mainContent?.getAttribute('data-identical-path') || jsonNull) ?? false;
   const isUserPage = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull) != null;
   const isTrashPage = _isTrashPage(path);
-  const isDeleted = JSON.parse(mainContent?.getAttribute('data-page-is-deleted') || jsonNull) ?? false;
   const isNotCreatable = JSON.parse(mainContent?.getAttribute('data-page-is-not-creatable') || jsonNull) ?? false;
   const isForbidden = forbiddenContent != null;
   const pageUser = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull);
@@ -136,7 +135,6 @@ const ContextExtractorOnce: FC = () => {
   useHasChildren(hasChildren);
   useHasDraftOnHackmd(hasDraftOnHackmd);
   useIsIdenticalPath(isIdenticalPath);
-  useIsDeleted(isDeleted);
   useIsNotCreatable(isNotCreatable);
   useIsForbidden(isForbidden);
   useIsTrashPage(isTrashPage);

+ 0 - 1
packages/app/src/client/services/PageContainer.js

@@ -61,7 +61,6 @@ export default class PageContainer extends Container {
 
       isUserPage: JSON.parse(mainContent.getAttribute('data-page-user')) != null,
       isTrashPage: isTrashPage(path),
-      isDeleted: JSON.parse(mainContent.getAttribute('data-page-is-deleted')),
       isNotCreatable: JSON.parse(mainContent.getAttribute('data-page-is-not-creatable')),
       isPageExist: mainContent.getAttribute('data-page-id') != null,
 

+ 1 - 0
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -153,6 +153,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
           <DropdownItem
             onClick={bookmarkItemClickedHandler}
             className="grw-page-control-dropdown-item"
+            data-testid="add-remove-bookmark-btn"
           >
             <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
             { pageInfo.isBookmarked ? t('remove_bookmark') : t('add_bookmark') }

+ 8 - 1
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -73,7 +73,13 @@ const GlobalSearch: FC<Props> = (props: Props) => {
     <div className={`form-group mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
       <div className="input-group flex-nowrap">
         <div className={`input-group-prepend ${dropup ? 'dropup' : ''}`}>
-          <button className="btn btn-secondary dropdown-toggle py-0" type="button" data-toggle="dropdown" aria-haspopup="true">
+          <button
+            className="btn btn-secondary dropdown-toggle py-0"
+            type="button"
+            data-toggle="dropdown"
+            aria-haspopup="true"
+            data-testid="select-search-scope"
+          >
             {scopeLabel}
           </button>
           <div className="dropdown-menu">
@@ -88,6 +94,7 @@ const GlobalSearch: FC<Props> = (props: Props) => {
               { t('header_search_box.item_label.All pages') }
             </button>
             <button
+              data-tesid="search-current-tree"
               className="dropdown-item"
               type="button"
               onClick={() => {

+ 2 - 3
packages/app/src/components/Page/TagsInput.tsx

@@ -38,11 +38,10 @@ const TagsInput: FC<Props> = (props: Props) => {
   const searchHandler = useCallback(async(query: string) => {
     const tagsSearchData = tagsSearch?.tags || [];
     setSearchQuery(query);
-
-    tagsSearchData.unshift(searchQuery);
+    tagsSearchData.unshift(query);
     setResultTags(Array.from(new Set(tagsSearchData)));
 
-  }, [searchQuery, tagsSearch?.tags]);
+  }, [tagsSearch?.tags]);
 
   const keyDownHandler = useCallback((event: React.KeyboardEvent) => {
     if (event.key === ' ') {

+ 4 - 3
packages/app/src/components/Page/TrashPageAlert.jsx

@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
 import { useTranslation } from 'react-i18next';
 
 import PageContainer from '~/client/services/PageContainer';
-import { useCurrentUpdatedAt, useShareLinkId } from '~/stores/context';
+import { useCurrentUpdatedAt, useIsTrashPage, useShareLinkId } from '~/stores/context';
 import { usePageDeleteModal, usePutBackPageModal } from '~/stores/modal';
 import { useSWRxPageInfo } from '~/stores/page';
 import { useIsAbleToShowTrashPageManagementButtons } from '~/stores/ui';
@@ -24,7 +24,7 @@ const TrashPageAlert = (props) => {
   const { t } = useTranslation();
   const { pageContainer } = props;
   const {
-    pageId, revisionId, path, isDeleted, lastUpdateUsername, deletedUserName, deletedAt,
+    pageId, revisionId, path, lastUpdateUsername, deletedUserName, deletedAt,
   } = pageContainer.state;
 
   const { data: isAbleToShowTrashPageManagementButtons } = useIsAbleToShowTrashPageManagementButtons();
@@ -38,6 +38,7 @@ const TrashPageAlert = (props) => {
   const { data: pageInfo } = useSWRxPageInfo(pageId ?? null, shareLinkId);
 
   const { data: updatedAt } = useCurrentUpdatedAt();
+  const { data: isTrashPage } = useIsTrashPage();
 
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openPutBackPageModal } = usePutBackPageModal();
@@ -89,7 +90,7 @@ const TrashPageAlert = (props) => {
       <div className="alert alert-warning py-3 pl-4 d-flex flex-column flex-lg-row">
         <div className="flex-grow-1">
           This page is in the trash <i className="icon-trash" aria-hidden="true"></i>.
-          {isDeleted && (
+          {isTrashPage && (
             <>
               <br />
               <UserPicture user={{ username: deletedUserName || lastUpdateUsername }} />

+ 1 - 1
packages/app/src/server/models/obsolete-page.js

@@ -98,7 +98,7 @@ export const getPageSchema = (crowi) => {
   }
 
   pageSchema.methods.isDeleted = function() {
-    return (this.status === STATUS_DELETED) || isTrashPage(this.path);
+    return isTrashPage(this.path);
   };
 
   pageSchema.methods.isPublic = function() {

+ 1 - 1
packages/app/src/server/routes/apiv3/pages.js

@@ -599,7 +599,7 @@ module.exports = (crowi) => {
   router.delete('/empty-trash', accessTokenParser, loginRequired, csrf, apiV3FormValidator, async(req, res) => {
     const options = {};
 
-    const pagesInTrash = await Page.findChildrenByParentPathOrIdAndViewer('/trash', req.user);
+    const pagesInTrash = await crowi.pageService.findChildrenByParentPathOrIdAndViewer('/trash', req.user);
 
     const deletablePages = crowi.pageService.filterPagesByCanDeleteCompletely(pagesInTrash, req.user, true);
 

+ 0 - 1
packages/app/src/server/views/layout-growi/identical-path-page.html

@@ -11,7 +11,6 @@
       data-path="{{ encodeURI(path) }}"
       data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
       data-page-is-not-creatable="true"
-      data-page-is-deleted="{% if page.isDeleted() %}true{% else %}false{% endif %}"
       data-identical-path="true"
     >
       <div class="flex-grow-1 flex-basis-0 mw-0">

+ 0 - 2
packages/app/src/server/views/widget/page_content.html

@@ -12,7 +12,6 @@
   data-page-grant="{{ grant }}"
   data-page-grant-group="{{ grantedGroupId }}"
   data-page-grant-group-name="{{ grantedGroupName }}"
-  data-page-is-deleted="{% if page.isDeleted() %}true{% else %}false{% endif %}"
   data-page-is-not-creatable="false"
   data-page-created-at="{{ page.createdAt|datetz('Y/m/d H:i:s') }}"
   data-page-creator="{% if page && page.creator %}{{ page.creator|json }}{% endif %}"
@@ -30,7 +29,6 @@
 <div id="content-main" class="content-main d-flex"
   data-path="{{ encodeURI(path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
-  data-page-is-deleted="{% if page.isDeleted() %}true{% else %}false{% endif %}"
   data-page-has-children="{% if pages.length > 0 %}true{% else %}false{% endif %}"
   >
 {% endif %}

+ 0 - 4
packages/app/src/stores/context.tsx

@@ -70,10 +70,6 @@ export const useIsTrashPage = (initialData?: boolean): SWRResponse<boolean, Erro
   return useStaticSWR<boolean, Error>('isTrashPage', initialData, { fallbackData: false });
 };
 
-export const useIsDeleted = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useStaticSWR<boolean, Error>('isDeleted', initialData, { fallbackData: false });
-};
-
 export const useIsNotCreatable = (initialData?: boolean): SWRResponse<boolean, Error> => {
   return useStaticSWR<boolean, Error>('isNotCreatable', initialData, { fallbackData: false });
 };

+ 3 - 3
packages/app/src/stores/ui.tsx

@@ -19,7 +19,7 @@ import loggerFactory from '~/utils/logger';
 
 import {
   useCurrentPageId, useCurrentPagePath, useIsEditable, useIsTrashPage, useIsUserPage, useIsGuestUser, useEmptyPageId,
-  useIsNotCreatable, useIsSharedUser, useNotFoundTargetPathOrId, useIsForbidden, useIsIdenticalPath, useIsNotFoundPermalink, useCurrentUser, useIsDeleted,
+  useIsNotCreatable, useIsSharedUser, useNotFoundTargetPathOrId, useIsForbidden, useIsIdenticalPath, useIsNotFoundPermalink, useCurrentUser,
 } from './context';
 import { localStorageMiddleware } from './middlewares/sync-to-storage';
 import { useStaticSWR } from './use-static-swr';
@@ -396,9 +396,9 @@ export const usePageTreeDescCountMap = (initialData?: UpdateDescCountData): SWRR
 
 export const useIsAbleToShowTrashPageManagementButtons = (): SWRResponse<boolean, Error> => {
   const { data: currentUser } = useCurrentUser();
-  const { data: isDeleted } = useIsDeleted();
+  const { data: isTrashPage } = useIsTrashPage();
 
-  return useStaticSWR('isAbleToShowTrashPageManagementButtons', isDeleted && currentUser != null);
+  return useStaticSWR('isAbleToShowTrashPageManagementButtons', isTrashPage && currentUser != null);
 };
 
 export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> => {

+ 174 - 0
packages/app/test/cypress/integration/30-search/search.spec.ts

@@ -68,3 +68,177 @@ context('Access to legacy private pages', () => {
   });
 
 });
+
+context('Search all pages', () => {
+  const ssPrefix = 'search-all-pages-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+    // collapse sidebar
+    cy.collapseSidebar(true);
+  });
+
+  it(`Search all pages by word is successfully loaded`, () => {
+    const searchText = 'help';
+
+    cy.visit('/');
+    cy.get('.rbt-input').click();
+    cy.get('.rbt-menu.dropdown-menu.show').should('be.visible').within(() => {
+      cy.screenshot(`${ssPrefix}1-search-input-focused`);
+    })
+
+    cy.get('.rbt-input-main').type(`${searchText}`);
+    cy.screenshot(`${ssPrefix}2-insert-search-text`, { capture: 'viewport'});
+    cy.get('.rbt-input-main').type('{enter}');
+
+
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.screenshot(`${ssPrefix}3-search-page-results`, { capture: 'viewport'});
+
+    cy.getByTestid('open-page-item-control-btn').eq(1).click();
+    cy.screenshot(`${ssPrefix}4-click-three-dots-menu`, {capture: 'viewport'});
+
+    //Add bookmark
+    cy.getByTestid('add-remove-bookmark-btn').click({force: true});
+    cy.get('.btn-bookmark.active').should('be.visible');
+    cy.screenshot(`${ssPrefix}5-add-bookmark`, {capture: 'viewport'});
+
+    // Duplicate page
+    cy.getByTestid('open-page-duplicate-modal-btn').first().click({force: true});
+    cy.getByTestid('page-duplicate-modal').should('be.visible');
+    cy.screenshot(`${ssPrefix}6-duplicate-page`, {capture: 'viewport'});
+
+    // Close Modal
+    cy.get('body').type('{esc}');
+
+    // Move / Rename Page
+    cy.getByTestid('open-page-move-rename-modal-btn').first().click({force: true});
+    cy.getByTestid('page-rename-modal').should('be.visible');
+    cy.screenshot(`${ssPrefix}7-move-rename-page`, {capture: 'viewport'});
+
+    // Close Modal
+    cy.get('body').type('{esc}');
+
+    // Delete page
+    cy.getByTestid('open-page-delete-modal-btn').first().click({ force: true});
+    cy.getByTestid('page-delete-modal').should('be.visible');
+    cy.screenshot(`${ssPrefix}8-delete-page`, {capture: 'viewport'});
+  });
+
+  it(`Search all pages by tag is successfully loaded `, () => {
+    const tag = 'help';
+    const searchText = `tag:${tag}`;
+    cy.visit('/');
+    // Add tag
+    cy.get('#edit-tags-btn-wrapper-for-tooltip > a').click({force: true});
+    cy.get('#edit-tag-modal').should('be.visible');
+
+    cy.get('#edit-tag-modal').within(() => {
+      cy.get('.rbt-input-main').type(tag);
+      cy.get('#tag-typeahead-asynctypeahead').should('be.visible');
+      cy.get('#tag-typeahead-asynctypeahead-item-0').should('be.visible');
+      cy.get('a#tag-typeahead-asynctypeahead-item-0').click({force: true})
+    });
+
+    cy.get('#edit-tag-modal').within(() => {
+      cy.get('div.modal-footer > button').click();
+    });
+
+    cy.visit('/');
+    cy.get('.rbt-input').click();
+    cy.get('.rbt-input-main').type(`${searchText}`);
+    cy.screenshot(`${ssPrefix}1-insert-search-text-with-tag`, { capture: 'viewport'});
+    cy.get('.rbt-input-main').type('{enter}');
+
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+
+    cy.screenshot(`${ssPrefix}2-search-with-tag-result`, {capture: 'viewport'});
+    cy.getByTestid('open-page-item-control-btn').first().click();
+    cy.screenshot(`${ssPrefix}3-click-three-dots-menu-search-with-tag`, {capture: 'viewport'});
+
+  });
+  it('Successfully order page search results by tag', () => {
+    const tag = 'help';
+
+    cy.visit('/');
+    cy.get('.grw-taglabels-container > form > a').contains(tag).click();
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.screenshot(`${ssPrefix}1-tag-order-click-tag-name`, {capture: 'viewport'});
+
+    cy.get('.grw-search-page-nav').within(() => {
+      cy.get('button.dropdown-toggle').first().click({force: true});
+      cy.get('.dropdown-menu-right').should('be.visible');
+      cy.get('.dropdown-menu-right > button:nth-child(1)').click({force: true});
+    });
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.screenshot(`${ssPrefix}2-tag-order-by-relevance`);
+
+    cy.get('.grw-search-page-nav').within(() => {
+      cy.get('button.dropdown-toggle').first().click({force: true});
+      cy.get('.dropdown-menu-right').should('be.visible');
+      cy.get('.dropdown-menu-right > button:nth-child(2)').click({force: true});
+    });
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.screenshot(`${ssPrefix}3-tag-order-by-creation-date`);
+
+    cy.get('.grw-search-page-nav').within(() => {
+      cy.get('button.dropdown-toggle').first().click({force: true});
+      cy.get('.dropdown-menu-right').should('be.visible');
+      cy.get('.dropdown-menu-right > button:nth-child(3)').click({force: true});
+    });
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.screenshot(`${ssPrefix}4-tag-order-by-last-update-date`);
+  });
+
+});
+
+context('Search current tree with "prefix":', () => {
+  const ssPrefix = 'search-current-tree-';
+
+  beforeEach(() => {
+    // login
+    cy.fixture("user-admin.json").then(user => {
+      cy.login(user.username, user.password);
+    });
+    // collapse sidebar
+    cy.collapseSidebar(true);
+  });
+
+  it(`Search current tree by word is successfully loaded`, () => {
+    const searchText = 'help';
+    cy.visit('/');
+    cy.getByTestid('select-search-scope').first().click({force: true});
+    cy.get('.input-group-prepend.show > div > button:nth-child(2)').click({force: true});
+    cy.get('.rbt-input').click();
+    cy.get('.rbt-menu.dropdown-menu.show').should('be.visible').within(() => {
+      cy.screenshot(`${ssPrefix}1-search-input-focused`);
+    })
+    cy.get('.rbt-input').type(`${searchText}`);
+    cy.screenshot(`${ssPrefix}2-insert-search-text`, { capture: 'viewport'});
+    cy.get('.rbt-input').type('{enter}');
+
+    cy.getByTestid('search-result-base').should('be.visible');
+    cy.getByTestid('search-result-list').should('be.visible');
+    cy.getByTestid('search-result-content').should('be.visible');
+    cy.screenshot(`${ssPrefix}3-search-page-results`, { capture: 'viewport'});
+
+    cy.getByTestid('open-page-item-control-btn').first().click();
+    cy.screenshot(`${ssPrefix}4-click-three-dots-menu`, {capture: 'viewport'});
+  });
+
+});