RefsContext.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import * as url from 'url';
  2. import { customTagUtils, pathUtils } from '@growi/core';
  3. const { TagContext, ArgsParser, OptionParser } = customTagUtils;
  4. const GRID_DEFAULT_TRACK_WIDTH = 64;
  5. const GRID_AVAILABLE_OPTIONS_LIST = [
  6. 'autofill',
  7. 'autofill-xs',
  8. 'autofill-sm',
  9. 'autofill-md',
  10. 'autofill-lg',
  11. 'autofill-xl',
  12. 'col-2',
  13. 'col-3',
  14. 'col-4',
  15. 'col-5',
  16. 'col-6',
  17. ];
  18. /**
  19. * Context Object class for $refs() and $refsimg()
  20. */
  21. export default class RefsContext extends TagContext {
  22. /**
  23. * @param {object|TagContext|RefsContext} initArgs
  24. */
  25. constructor(initArgs, fromPagePath) {
  26. super(initArgs);
  27. this.fromPagePath = fromPagePath;
  28. // initialized after parse()
  29. this.pagePath = null;
  30. this.isParsed = null;
  31. this.options = {};
  32. }
  33. get isSingle() {
  34. return this.method.match(/^(ref|refimg)$/);
  35. }
  36. get isExtractImg() {
  37. return this.method.match(/^(refimg|refsimg)$/);
  38. }
  39. parse() {
  40. if (this.isParsed) {
  41. return;
  42. }
  43. const parsedArgs = ArgsParser.parse(this.args);
  44. this.options = Object.assign(this.options, parsedArgs.options);
  45. if (this.isSingle) {
  46. this.parseForSingle(parsedArgs);
  47. }
  48. else {
  49. this.parseForMulti(parsedArgs);
  50. }
  51. this.isParsed = true;
  52. }
  53. /**
  54. * parse method for 'ref' and 'refimg'
  55. */
  56. parseForSingle(parsedArgs) {
  57. // determine fileNameOrId
  58. // order:
  59. // 1: ref(file=..., ...)
  60. // 2: ref(id=..., ...)
  61. // 2: refs(firstArgs, ...)
  62. this.fileNameOrId = this.options.file || this.options.id
  63. || ((parsedArgs.firstArgsValue === true) ? parsedArgs.firstArgsKey : undefined);
  64. if (this.fileNameOrId == null) {
  65. throw new Error('\'file\' or \'id\' is not specified. Set first argument or specify \'file\' or \'id\' option');
  66. }
  67. // determine pagePath
  68. // order:
  69. // 1: ref(page=..., ...)
  70. // 2: constructor argument
  71. const specifiedPath = this.options.page || this.fromPagePath;
  72. this.pagePath = this.getAbsolutePathFor(specifiedPath);
  73. }
  74. /**
  75. * parse method for 'refs' and 'refsimg'
  76. */
  77. parseForMulti(parsedArgs) {
  78. if (this.options.prefix) {
  79. this.prefix = this.resolvePath(this.options.prefix);
  80. }
  81. else {
  82. // determine pagePath
  83. // order:
  84. // 1: ref(page=..., ...)
  85. // 2: refs(firstArgs, ...)
  86. // 3: constructor argument
  87. const specifiedPath = this.options.page
  88. || ((parsedArgs.firstArgsValue === true) ? parsedArgs.firstArgsKey : undefined)
  89. || this.fromPagePath;
  90. this.pagePath = this.getAbsolutePathFor(specifiedPath);
  91. }
  92. if (this.options.grid != null && this.getOptGrid() == null) {
  93. throw new Error('\'grid\' option is invalid. '
  94. + 'Available value is: \'autofill-( xs | sm | md | lg | xl )\' or \'col-( 2 | 3 | 4 | 5 | 6 )\'');
  95. }
  96. }
  97. // resolve pagePath
  98. // when `fromPagePath`=/hoge and `specifiedPath`=./fuga,
  99. // `pagePath` to be /hoge/fuga
  100. // when `fromPagePath`=/hoge and `specifiedPath`=/fuga,
  101. // `pagePath` to be /fuga
  102. // when `fromPagePath`=/hoge and `specifiedPath`=undefined,
  103. // `pagePath` to be /hoge
  104. resolvePath(pagePath) {
  105. return decodeURIComponent(url.resolve(pathUtils.addTrailingSlash(this.fromPagePath), pagePath));
  106. }
  107. getOptDepth() {
  108. if (this.options.depth === undefined) {
  109. return undefined;
  110. }
  111. return OptionParser.parseRange(this.options.depth);
  112. }
  113. getOptGrid() {
  114. const { grid } = this.options;
  115. return GRID_AVAILABLE_OPTIONS_LIST.find(item => item === grid);
  116. }
  117. isOptGridColumnEnabled() {
  118. const optGrid = this.getOptGrid();
  119. return (optGrid != null) && optGrid.startsWith('col-');
  120. }
  121. getOptGridColumnsNum() {
  122. const { grid } = this.options;
  123. let columnsNum = null;
  124. switch (grid) {
  125. case 'col-2':
  126. columnsNum = 2;
  127. break;
  128. case 'col-3':
  129. columnsNum = 3;
  130. break;
  131. case 'col-4':
  132. columnsNum = 4;
  133. break;
  134. case 'col-5':
  135. columnsNum = 5;
  136. break;
  137. case 'col-6':
  138. columnsNum = 6;
  139. break;
  140. }
  141. return columnsNum;
  142. }
  143. /**
  144. * return auto-calculated grid width
  145. * rules:
  146. * 1. when column mode (e.g. col-6, col5, ...), the width specification is disabled
  147. * 2. when width option is set, return it
  148. * 3. otherwise, the mode should be autofill and the width will be calculated according to the size
  149. */
  150. getOptGridWidth() {
  151. const grid = this.getOptGrid();
  152. const { width } = this.options;
  153. // when Grid column mode
  154. if (this.isOptGridColumnEnabled()) {
  155. // not specify and ignore width
  156. return undefined;
  157. }
  158. // when width is specified
  159. if (width != null) {
  160. return width;
  161. }
  162. // when Grid autofill mode
  163. let autofillMagnification = 1;
  164. switch (grid) {
  165. case 'autofill-xl':
  166. autofillMagnification = 3;
  167. break;
  168. case 'autofill-lg':
  169. autofillMagnification = 2;
  170. break;
  171. case 'autofill-sm':
  172. autofillMagnification = 0.75;
  173. break;
  174. case 'autofill-xs':
  175. autofillMagnification = 0.5;
  176. break;
  177. case 'autofill':
  178. case 'autofill-md':
  179. break;
  180. }
  181. return `${GRID_DEFAULT_TRACK_WIDTH * autofillMagnification}px`;
  182. }
  183. /**
  184. * return auto-calculated grid height
  185. * rules:
  186. * 1. when height option is set, return it
  187. * 2. otherwise, the same value to the width will be returned
  188. */
  189. getOptGridHeight() {
  190. const { height } = this.options;
  191. // when height is specified
  192. if (height != null) {
  193. return height;
  194. }
  195. // return the value which is same to width
  196. return this.getOptGridWidth();
  197. }
  198. /**
  199. * return absolute path for the specified path
  200. *
  201. * @param {string} relativePath relative path from `this.fromPagePath`
  202. */
  203. getAbsolutePathFor(relativePath) {
  204. return decodeURIComponent(
  205. pathUtils.normalizePath( // normalize like /foo/bar
  206. url.resolve(pathUtils.addTrailingSlash(this.fromPagePath), relativePath),
  207. ),
  208. );
  209. }
  210. }