page-operation.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { SubscriptionStatusType, Nullable } from '@growi/core';
  2. import urljoin from 'url-join';
  3. import { OptionsToSave } from '~/interfaces/editor-settings';
  4. import loggerFactory from '~/utils/logger';
  5. import { toastError } from '../util/apiNotification';
  6. import { apiPost } from '../util/apiv1-client';
  7. import { apiv3Post, apiv3Put } from '../util/apiv3-client';
  8. const logger = loggerFactory('growi:services:page-operation');
  9. export const toggleSubscribe = async(pageId: string, currentStatus: SubscriptionStatusType | undefined): Promise<void> => {
  10. try {
  11. const newStatus = currentStatus === SubscriptionStatusType.SUBSCRIBE
  12. ? SubscriptionStatusType.UNSUBSCRIBE
  13. : SubscriptionStatusType.SUBSCRIBE;
  14. await apiv3Put('/page/subscribe', { pageId, status: newStatus });
  15. }
  16. catch (err) {
  17. toastError(err);
  18. }
  19. };
  20. export const toggleLike = async(pageId: string, currentValue?: boolean): Promise<void> => {
  21. try {
  22. await apiv3Put('/page/likes', { pageId, bool: !currentValue });
  23. }
  24. catch (err) {
  25. toastError(err);
  26. }
  27. };
  28. export const toggleBookmark = async(pageId: string, currentValue?: boolean): Promise<void> => {
  29. try {
  30. await apiv3Put('/bookmarks', { pageId, bool: !currentValue });
  31. }
  32. catch (err) {
  33. toastError(err);
  34. }
  35. };
  36. // Utility to update body class
  37. const updateBodyClassByView = (expandContentWidth: boolean): void => {
  38. const bodyClasses = document.body.classList;
  39. const isLayoutFluid = bodyClasses.contains('growi-layout-fluid');
  40. if (expandContentWidth && !isLayoutFluid) {
  41. bodyClasses.add('growi-layout-fluid');
  42. }
  43. else if (isLayoutFluid) {
  44. bodyClasses.remove('growi-layout-fluid');
  45. }
  46. };
  47. export const updateContentWidth = async(pageId: string, newValue: boolean): Promise<void> => {
  48. try {
  49. await apiv3Put(`/page/${pageId}/content-width`, { expandContentWidth: newValue });
  50. updateBodyClassByView(newValue);
  51. }
  52. catch (err) {
  53. toastError(err);
  54. }
  55. };
  56. export const bookmark = async(pageId: string): Promise<void> => {
  57. try {
  58. await apiv3Put('/bookmarks', { pageId, bool: true });
  59. }
  60. catch (err) {
  61. toastError(err);
  62. }
  63. };
  64. export const unbookmark = async(pageId: string): Promise<void> => {
  65. try {
  66. await apiv3Put('/bookmarks', { pageId, bool: false });
  67. }
  68. catch (err) {
  69. toastError(err);
  70. }
  71. };
  72. export const exportAsMarkdown = (pageId: string, revisionId: string, format: string): void => {
  73. const url = new URL(urljoin(window.location.origin, '_api/v3/page/export', pageId));
  74. url.searchParams.append('format', format);
  75. url.searchParams.append('revisionId', revisionId);
  76. window.location.href = url.href;
  77. };
  78. /**
  79. * send request to fix broken paths caused by unexpected events such as server shutdown while renaming page paths
  80. */
  81. export const resumeRenameOperation = async(pageId: string): Promise<void> => {
  82. await apiv3Post('/pages/resume-rename', { pageId });
  83. };
  84. // TODO: define return type
  85. const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
  86. // clone
  87. const params = Object.assign(tmpParams, {
  88. path: pagePath,
  89. body: markdown,
  90. });
  91. const res = await apiv3Post('/pages/', params);
  92. const { page, tags, revision } = res.data;
  93. return { page, tags, revision };
  94. };
  95. // TODO: define return type
  96. const updatePage = async(pageId: string, revisionId: string, markdown: string, tmpParams: OptionsToSave) => {
  97. // clone
  98. const params = Object.assign(tmpParams, {
  99. page_id: pageId,
  100. revision_id: revisionId,
  101. body: markdown,
  102. });
  103. const res: any = await apiPost('/pages.update', params);
  104. if (!res.ok) {
  105. throw new Error(res.error);
  106. }
  107. return res;
  108. };
  109. type PageInfo= {
  110. path: string,
  111. pageId: Nullable<string>,
  112. revisionId: Nullable<string>,
  113. }
  114. // TODO: define return type
  115. export const saveOrUpdate = async(optionsToSave: OptionsToSave, pageInfo: PageInfo, markdown: string) => {
  116. const { path, pageId, revisionId } = pageInfo;
  117. const options = Object.assign({}, optionsToSave);
  118. /*
  119. * Note: variable "markdown" will be received from params
  120. * please delete the following code after implemating HackMD editor function
  121. */
  122. // let markdown;
  123. // if (editorMode === EditorMode.HackMD) {
  124. // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
  125. // markdown = await pageEditorByHackmd.getMarkdown();
  126. // // set option to sync
  127. // options.isSyncRevisionToHackmd = true;
  128. // revisionId = this.state.revisionIdHackmdSynced;
  129. // }
  130. // else {
  131. // const pageEditor = this.appContainer.getComponentInstance('PageEditor');
  132. // const pageEditor = getComponentInstance('PageEditor');
  133. // markdown = pageEditor.getMarkdown();
  134. // }
  135. let res;
  136. if (pageId == null) {
  137. res = await createPage(path, markdown, options);
  138. }
  139. else {
  140. if (revisionId == null) {
  141. const msg = '\'revisionId\' is required to update page';
  142. throw new Error(msg);
  143. }
  144. res = await updatePage(pageId, revisionId, markdown, options);
  145. }
  146. return res;
  147. };