AdminCustomizeContainer.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import { Container } from 'unstated';
  2. import loggerFactory from '@alias/logger';
  3. import { toastError } from '../util/apiNotification';
  4. // eslint-disable-next-line no-unused-vars
  5. const logger = loggerFactory('growi:services:AdminCustomizeContainer');
  6. /**
  7. * Service container for admin customize setting page (Customize.jsx)
  8. * @extends {Container} unstated Container
  9. */
  10. export default class AdminCustomizeContainer extends Container {
  11. constructor(appContainer) {
  12. super();
  13. this.appContainer = appContainer;
  14. this.dummyCurrentTheme = 0;
  15. this.dummyCurrentThemeForError = 1;
  16. this.state = {
  17. retrieveError: null,
  18. // set dummy value tile for using suspense
  19. currentTheme: this.dummyCurrentTheme,
  20. currentLayout: '',
  21. isEnabledTimeline: false,
  22. isSavedStatesOfTabChanges: false,
  23. isEnabledAttachTitleHeader: false,
  24. currentRecentCreatedLimit: 10,
  25. isEnabledStaleNotification: false,
  26. isAllReplyShown: false,
  27. currentHighlightJsStyleId: '',
  28. isHighlightJsStyleBorderEnabled: false,
  29. currentCustomizeTitle: '',
  30. currentCustomizeHeader: '',
  31. currentCustomizeCss: '',
  32. currentCustomizeScript: '',
  33. /* eslint-disable quote-props, no-multi-spaces */
  34. highlightJsCssSelectorOptions: {
  35. 'github': { name: '[Light] GitHub', border: false },
  36. 'github-gist': { name: '[Light] GitHub Gist', border: true },
  37. 'atom-one-light': { name: '[Light] Atom One Light', border: true },
  38. 'xcode': { name: '[Light] Xcode', border: true },
  39. 'vs': { name: '[Light] Vs', border: true },
  40. 'atom-one-dark': { name: '[Dark] Atom One Dark', border: false },
  41. 'hybrid': { name: '[Dark] Hybrid', border: false },
  42. 'monokai': { name: '[Dark] Monokai', border: false },
  43. 'tomorrow-night': { name: '[Dark] Tomorrow Night', border: false },
  44. 'vs2015': { name: '[Dark] Vs 2015', border: false },
  45. },
  46. /* eslint-enable quote-props, no-multi-spaces */
  47. };
  48. }
  49. /**
  50. * Workaround for the mangling in production build to break constructor.name
  51. */
  52. static getClassName() {
  53. return 'AdminCustomizeContainer';
  54. }
  55. /**
  56. * retrieve customize data
  57. */
  58. async retrieveCustomizeData() {
  59. try {
  60. const response = await this.appContainer.apiv3.get('/customize-setting/');
  61. const { customizeParams } = response.data;
  62. this.setState({
  63. currentTheme: customizeParams.themeType,
  64. currentLayout: customizeParams.layoutType,
  65. isEnabledTimeline: customizeParams.isEnabledTimeline,
  66. isSavedStatesOfTabChanges: customizeParams.isSavedStatesOfTabChanges,
  67. isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
  68. currentRecentCreatedLimit: customizeParams.recentCreatedLimit,
  69. isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
  70. isAllReplyShown: customizeParams.isAllReplyShown,
  71. currentHighlightJsStyleId: customizeParams.styleName,
  72. isHighlightJsStyleBorderEnabled: customizeParams.styleBorder,
  73. currentCustomizeTitle: customizeParams.customizeTitle,
  74. currentCustomizeHeader: customizeParams.customizeHeader,
  75. currentCustomizeCss: customizeParams.customizeCss,
  76. currentCustomizeScript: customizeParams.customizeScript,
  77. });
  78. // search style name from object for display
  79. this.setState({ currentHighlightJsStyleName: this.state.highlightJsCssSelectorOptions[customizeParams.styleName].name });
  80. }
  81. catch (err) {
  82. this.setState({ retrieveError: err });
  83. logger.error(err);
  84. throw new Error('Failed to fetch data');
  85. }
  86. }
  87. /**
  88. * Switch layoutType
  89. */
  90. switchLayoutType(lauoutName) {
  91. this.setState({ currentLayout: lauoutName });
  92. }
  93. /**
  94. * Switch themeType
  95. */
  96. switchThemeType(themeName) {
  97. // can't choose theme when kibela
  98. if (this.state.currentLayout === 'kibela') {
  99. return;
  100. }
  101. this.setState({ currentTheme: themeName });
  102. // preview if production
  103. if (process.env.NODE_ENV !== 'development') {
  104. this.previewTheme(themeName);
  105. }
  106. }
  107. /**
  108. * Switch enabledTimeLine
  109. */
  110. switchEnableTimeline() {
  111. this.setState({ isEnabledTimeline: !this.state.isEnabledTimeline });
  112. }
  113. /**
  114. * Switch savedStatesOfTabChanges
  115. */
  116. switchSavedStatesOfTabChanges() {
  117. this.setState({ isSavedStatesOfTabChanges: !this.state.isSavedStatesOfTabChanges });
  118. }
  119. /**
  120. * Switch enabledAttachTitleHeader
  121. */
  122. switchEnabledAttachTitleHeader() {
  123. this.setState({ isEnabledAttachTitleHeader: !this.state.isEnabledAttachTitleHeader });
  124. }
  125. /**
  126. * Switch recentCreatedLimit
  127. */
  128. switchRecentCreatedLimit(value) {
  129. this.setState({ currentRecentCreatedLimit: value });
  130. }
  131. /**
  132. * Switch enabledStaleNotification
  133. */
  134. switchEnableStaleNotification() {
  135. this.setState({ isEnabledStaleNotification: !this.state.isEnabledStaleNotification });
  136. }
  137. /**
  138. * Switch isAllReplyShown
  139. */
  140. switchIsAllReplyShown() {
  141. this.setState({ isAllReplyShown: !this.state.isAllReplyShown });
  142. }
  143. /**
  144. * Switch highlightJsStyle
  145. */
  146. switchHighlightJsStyle(styleId, styleName, isBorderEnable) {
  147. this.setState({ currentHighlightJsStyleId: styleId });
  148. this.setState({ currentHighlightJsStyleName: styleName });
  149. // recommended settings are applied
  150. this.setState({ isHighlightJsStyleBorderEnabled: isBorderEnable });
  151. this.previewHighlightJsStyle(styleId);
  152. }
  153. /**
  154. * Switch highlightJsStyleBorder
  155. */
  156. switchHighlightJsStyleBorder() {
  157. this.setState({ isHighlightJsStyleBorderEnabled: !this.state.isHighlightJsStyleBorderEnabled });
  158. }
  159. /**
  160. * Change customize Title
  161. */
  162. changeCustomizeTitle(inputValue) {
  163. this.setState({ currentCustomizeTitle: inputValue });
  164. }
  165. /**
  166. * Change customize Html header
  167. */
  168. changeCustomizeHeader(inputValue) {
  169. this.setState({ currentCustomizeHeader: inputValue });
  170. }
  171. /**
  172. * Change customize css
  173. */
  174. changeCustomizeCss(inputValue) {
  175. this.setState({ currentCustomizeCss: inputValue });
  176. }
  177. /**
  178. * Change customize script
  179. */
  180. changeCustomizeScript(inpuValue) {
  181. this.setState({ currentCustomizeScript: inpuValue });
  182. }
  183. /**
  184. * Preview theme
  185. * @param {string} themeName
  186. */
  187. async previewTheme(themeName) {
  188. try {
  189. // get theme asset path
  190. const response = await this.appContainer.apiv3.get('/customize-setting/layout-theme/asset-path', { themeName });
  191. const { assetPath } = response.data;
  192. const themeLink = document.getElementById('grw-theme-link');
  193. themeLink.setAttribute('href', assetPath);
  194. }
  195. catch (err) {
  196. toastError(err);
  197. }
  198. }
  199. /**
  200. * Preview hljs style
  201. * @param {string} styleId
  202. */
  203. previewHighlightJsStyle(styleId) {
  204. const styleLInk = document.querySelectorAll('#grw-hljs-container-for-demo link')[0];
  205. // replace css url
  206. // see https://regex101.com/r/gBNZYu/4
  207. styleLInk.href = styleLInk.href.replace(/[^/]+\.css$/, `${styleId}.css`);
  208. }
  209. /**
  210. * Update layout
  211. * @memberOf AdminCustomizeContainer
  212. */
  213. async updateCustomizeLayoutAndTheme() {
  214. try {
  215. const response = await this.appContainer.apiv3.put('/customize-setting/layout-theme', {
  216. layoutType: this.state.currentLayout,
  217. themeType: this.state.currentTheme,
  218. });
  219. const { customizedParams } = response.data;
  220. this.setState({
  221. layoutType: customizedParams.layoutType,
  222. themeType: customizedParams.themeType,
  223. });
  224. }
  225. catch (err) {
  226. logger.error(err);
  227. throw new Error('Failed to update data');
  228. }
  229. }
  230. /**
  231. * Update function
  232. * @memberOf AdminCustomizeContainer
  233. */
  234. async updateCustomizeFunction() {
  235. try {
  236. const response = await this.appContainer.apiv3.put('/customize-setting/function', {
  237. isEnabledTimeline: this.state.isEnabledTimeline,
  238. isSavedStatesOfTabChanges: this.state.isSavedStatesOfTabChanges,
  239. isEnabledAttachTitleHeader: this.state.isEnabledAttachTitleHeader,
  240. recentCreatedLimit: this.state.currentRecentCreatedLimit,
  241. isEnabledStaleNotification: this.state.isEnabledStaleNotification,
  242. isAllReplyShown: this.state.isAllReplyShown,
  243. });
  244. const { customizedParams } = response.data;
  245. this.setState({
  246. isEnabledTimeline: customizedParams.isEnabledTimeline,
  247. isSavedStatesOfTabChanges: customizedParams.isSavedStatesOfTabChanges,
  248. isEnabledAttachTitleHeader: customizedParams.isEnabledAttachTitleHeader,
  249. recentCreatedLimit: customizedParams.currentRecentCreatedLimit,
  250. isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
  251. isAllReplyShown: customizedParams.isAllReplyShown,
  252. });
  253. }
  254. catch (err) {
  255. logger.error(err);
  256. throw new Error('Failed to update data');
  257. }
  258. }
  259. /**
  260. * Update code highlight
  261. * @memberOf AdminCustomizeContainer
  262. */
  263. async updateHighlightJsStyle() {
  264. try {
  265. const response = await this.appContainer.apiv3.put('/customize-setting/highlight', {
  266. highlightJsStyle: this.state.currentHighlightJsStyleId,
  267. highlightJsStyleBorder: this.state.isHighlightJsStyleBorderEnabled,
  268. });
  269. const { customizedParams } = response.data;
  270. this.setState({
  271. highlightJsStyle: customizedParams.highlightJsStyle,
  272. highlightJsStyleBorder: customizedParams.highlightJsStyleBorder,
  273. });
  274. }
  275. catch (err) {
  276. logger.error(err);
  277. throw new Error('Failed to update data');
  278. }
  279. }
  280. /**
  281. * Update customTitle
  282. * @memberOf AdminCustomizeContainer
  283. */
  284. async updateCustomizeTitle() {
  285. try {
  286. const response = await this.appContainer.apiv3.put('/customize-setting/customize-title', {
  287. customizeTitle: this.state.currentCustomizeTitle,
  288. });
  289. const { customizedParams } = response.data;
  290. this.setState({
  291. customizeTitle: customizedParams.customizeTitle,
  292. });
  293. }
  294. catch (err) {
  295. logger.error(err);
  296. throw new Error('Failed to update data');
  297. }
  298. }
  299. /**
  300. * Update customHeader
  301. * @memberOf AdminCustomizeContainer
  302. */
  303. async updateCustomizeHeader() {
  304. try {
  305. const response = await this.appContainer.apiv3.put('/customize-setting/customize-header', {
  306. customizeHeader: this.state.currentCustomizeHeader,
  307. });
  308. const { customizedParams } = response.data;
  309. this.setState({
  310. currentCustomizeHeader: customizedParams.customizeHeader,
  311. });
  312. }
  313. catch (err) {
  314. logger.error(err);
  315. throw new Error('Failed to update data');
  316. }
  317. }
  318. /**
  319. * Update customCss
  320. * @memberOf AdminCustomizeContainer
  321. */
  322. async updateCustomizeCss() {
  323. try {
  324. const response = await this.appContainer.apiv3.put('/customize-setting/customize-css', {
  325. customizeCss: this.state.currentCustomizeCss,
  326. });
  327. const { customizedParams } = response.data;
  328. this.setState({
  329. currentCustomizeCss: customizedParams.customizeCss,
  330. });
  331. }
  332. catch (err) {
  333. logger.error(err);
  334. throw new Error('Failed to update data');
  335. }
  336. }
  337. /**
  338. * Update customize script
  339. * @memberOf AdminCustomizeContainer
  340. * @return {string} Customize scripts
  341. */
  342. async updateCustomizeScript() {
  343. try {
  344. const response = await this.appContainer.apiv3.put('/customize-setting/customize-script', {
  345. customizeScript: this.state.currentCustomizeScript,
  346. });
  347. const { customizedParams } = response.data;
  348. this.setState({
  349. currentCustomizeScript: customizedParams.customizeScript,
  350. });
  351. }
  352. catch (err) {
  353. logger.error(err);
  354. throw new Error('Failed to update data');
  355. }
  356. }
  357. }