page-operation.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import { useCallback } from 'react';
  2. import type { IPageHasId } from '@growi/core';
  3. import { SubscriptionStatusType } from '@growi/core';
  4. import urljoin from 'url-join';
  5. import type {
  6. IApiv3PageCreateParams, IApiv3PageCreateResponse, IApiv3PageUpdateParams, IApiv3PageUpdateResponse,
  7. } from '~/interfaces/apiv3';
  8. import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
  9. import { useCurrentPageId, useSWRMUTxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
  10. import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
  11. import loggerFactory from '~/utils/logger';
  12. import { apiPost } from '../util/apiv1-client';
  13. import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
  14. import { toastError } from '../util/toastr';
  15. const logger = loggerFactory('growi:services:page-operation');
  16. export const toggleSubscribe = async(pageId: string, currentStatus: SubscriptionStatusType | undefined): Promise<void> => {
  17. try {
  18. const newStatus = currentStatus === SubscriptionStatusType.SUBSCRIBE
  19. ? SubscriptionStatusType.UNSUBSCRIBE
  20. : SubscriptionStatusType.SUBSCRIBE;
  21. await apiv3Put('/page/subscribe', { pageId, status: newStatus });
  22. }
  23. catch (err) {
  24. toastError(err);
  25. }
  26. };
  27. export const toggleLike = async(pageId: string, currentValue?: boolean): Promise<void> => {
  28. try {
  29. await apiv3Put('/page/likes', { pageId, bool: !currentValue });
  30. }
  31. catch (err) {
  32. toastError(err);
  33. }
  34. };
  35. export const toggleBookmark = async(pageId: string, currentValue?: boolean): Promise<void> => {
  36. try {
  37. await apiv3Put('/bookmarks', { pageId, bool: !currentValue });
  38. }
  39. catch (err) {
  40. toastError(err);
  41. }
  42. };
  43. export const updateContentWidth = async(pageId: string, newValue: boolean): Promise<void> => {
  44. try {
  45. await apiv3Put(`/page/${pageId}/content-width`, { expandContentWidth: newValue });
  46. }
  47. catch (err) {
  48. toastError(err);
  49. }
  50. };
  51. export const bookmark = async(pageId: string): Promise<void> => {
  52. try {
  53. await apiv3Put('/bookmarks', { pageId, bool: true });
  54. }
  55. catch (err) {
  56. toastError(err);
  57. }
  58. };
  59. export const unbookmark = async(pageId: string): Promise<void> => {
  60. try {
  61. await apiv3Put('/bookmarks', { pageId, bool: false });
  62. }
  63. catch (err) {
  64. toastError(err);
  65. }
  66. };
  67. export const exportAsMarkdown = (pageId: string, revisionId: string, format: string): void => {
  68. const url = new URL(urljoin(window.location.origin, '_api/v3/page/export', pageId));
  69. url.searchParams.append('format', format);
  70. url.searchParams.append('revisionId', revisionId);
  71. window.location.href = url.href;
  72. };
  73. /**
  74. * send request to fix broken paths caused by unexpected events such as server shutdown while renaming page paths
  75. */
  76. export const resumeRenameOperation = async(pageId: string): Promise<void> => {
  77. await apiv3Post('/pages/resume-rename', { pageId });
  78. };
  79. export const createPage = async(params: IApiv3PageCreateParams): Promise<IApiv3PageCreateResponse> => {
  80. const res = await apiv3Post<IApiv3PageCreateResponse>('/page', params);
  81. return res.data;
  82. };
  83. export const updatePage = async(params: IApiv3PageUpdateParams): Promise<IApiv3PageUpdateResponse> => {
  84. const res = await apiv3Put<IApiv3PageUpdateResponse>('/page', params);
  85. return res.data;
  86. };
  87. export type UpdateStateAfterSaveOption = {
  88. supressEditingMarkdownMutation: boolean,
  89. }
  90. export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: UpdateStateAfterSaveOption): (() => Promise<void>) | undefined => {
  91. const { mutate: mutateCurrentPageId } = useCurrentPageId();
  92. const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
  93. const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
  94. const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
  95. const { sync: syncTagsInfoForEditor } = usePageTagsForEditors(pageId);
  96. const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
  97. // update swr 'currentPageId', 'currentPage', remote states
  98. return useCallback(async() => {
  99. if (pageId == null) { return }
  100. // update tag before page: https://github.com/weseek/growi/pull/7158
  101. // !! DO NOT CHANGE THE ORDERS OF THE MUTATIONS !! -- 12.26 yuken-t
  102. await mutateTagsInfo(); // get from DB
  103. syncTagsInfoForEditor(); // sync global state for client
  104. await mutateCurrentPageId(pageId);
  105. const updatedPage = await mutateCurrentPage();
  106. if (updatedPage == null || updatedPage.revision == null) { return }
  107. // supress to mutate only when updated from built-in editor
  108. // and see: https://github.com/weseek/growi/pull/7118
  109. const supressEditingMarkdownMutation = opts?.supressEditingMarkdownMutation ?? false;
  110. if (!supressEditingMarkdownMutation) {
  111. mutateEditingMarkdown(updatedPage.revision.body);
  112. }
  113. const remoterevisionData = {
  114. remoteRevisionId: updatedPage.revision._id,
  115. remoteRevisionBody: updatedPage.revision.body,
  116. remoteRevisionLastUpdateUser: updatedPage.lastUpdateUser,
  117. remoteRevisionLastUpdatedAt: updatedPage.updatedAt,
  118. };
  119. setRemoteLatestPageData(remoterevisionData);
  120. },
  121. // eslint-disable-next-line max-len
  122. [pageId, mutateTagsInfo, syncTagsInfoForEditor, mutateCurrentPageId, mutateCurrentPage, opts?.supressEditingMarkdownMutation, setRemoteLatestPageData, mutateEditingMarkdown]);
  123. };
  124. export const unlink = async(path: string): Promise<void> => {
  125. await apiPost('/pages.unlink', { path });
  126. };
  127. interface PageExistResponse {
  128. isExist: boolean,
  129. }
  130. export const exist = async(path: string): Promise<PageExistResponse> => {
  131. const res = await apiv3Get<PageExistResponse>('/page/exist', { path });
  132. return res.data;
  133. };
  134. export const publish = async(pageId: string): Promise<IPageHasId> => {
  135. const res = await apiv3Put(`/page/${pageId}/publish`);
  136. return res.data;
  137. };
  138. export const unpublish = async(pageId: string): Promise<IPageHasId> => {
  139. const res = await apiv3Put(`/page/${pageId}/unpublish`);
  140. return res.data;
  141. };