docs.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. var docsApp = {
  2. controller: {},
  3. directive: {},
  4. serviceFactory: {}
  5. };
  6. docsApp.directive.ngHtmlWrapLoaded = function(reindentCode, templateMerge, loadedUrls) {
  7. function escape(text) {
  8. return text.
  9. replace(/\&/g, '&').
  10. replace(/\</g, '&lt;').
  11. replace(/\>/g, '&gt;').
  12. replace(/"/g, '&quot;');
  13. }
  14. function setHtmlIe8SafeWay(element, html) {
  15. var newElement = angular.element('<pre>' + html + '</pre>');
  16. element.html('');
  17. element.append(newElement.contents());
  18. return element;
  19. }
  20. return {
  21. compile: function(element, attr) {
  22. var properties = {
  23. head: '',
  24. module: '',
  25. body: element.text()
  26. },
  27. html = "<!doctype html>\n<html ng-app{{module}}>\n <head>\n{{head:4}} </head>\n <body>\n{{body:4}} </body>\n</html>";
  28. angular.forEach(loadedUrls.base, function(dep) {
  29. properties.head += '<script src="' + dep + '"></script>\n';
  30. });
  31. angular.forEach((attr.ngHtmlWrapLoaded || '').split(' '), function(dep) {
  32. if (!dep) return;
  33. var ext = dep.split(/\./).pop();
  34. if (ext == 'css') {
  35. properties.head += '<link rel="stylesheet" href="' + dep + '" type="text/css">\n';
  36. } else if(ext == 'js' && dep !== 'angular.js') {
  37. properties.head += '<script src="' + (loadedUrls[dep] || dep) + '"></script>\n';
  38. } else if (dep !== 'angular.js') {
  39. properties.module = '="' + dep + '"';
  40. }
  41. });
  42. setHtmlIe8SafeWay(element, escape(templateMerge(html, properties)));
  43. }
  44. };
  45. };
  46. docsApp.directive.focused = function($timeout) {
  47. return function(scope, element, attrs) {
  48. element[0].focus();
  49. element.on('focus', function() {
  50. scope.$apply(attrs.focused + '=true');
  51. });
  52. element.on('blur', function() {
  53. // have to use $timeout, so that we close the drop-down after the user clicks,
  54. // otherwise when the user clicks we process the closing before we process the click.
  55. $timeout(function() {
  56. scope.$eval(attrs.focused + '=false');
  57. });
  58. });
  59. scope.$eval(attrs.focused + '=true');
  60. };
  61. };
  62. docsApp.directive.code = function() {
  63. return { restrict:'E', terminal: true };
  64. };
  65. docsApp.directive.sourceEdit = function(getEmbeddedTemplate) {
  66. return NG_DOCS.editExample ? {
  67. template: '<a class="edit-example pull-right" ng-click="plunkr($event)" href>' +
  68. '<i class="icon-edit"></i> Edit in Plunkr</a>',
  69. scope: true,
  70. controller: function($scope, $attrs, openPlunkr) {
  71. var sources = {
  72. module: $attrs.sourceEdit,
  73. deps: read($attrs.sourceEditDeps),
  74. html: read($attrs.sourceEditHtml),
  75. css: read($attrs.sourceEditCss),
  76. js: read($attrs.sourceEditJs),
  77. unit: read($attrs.sourceEditUnit),
  78. scenario: read($attrs.sourceEditScenario)
  79. };
  80. $scope.plunkr = function(e) {
  81. e.stopPropagation();
  82. openPlunkr(sources);
  83. };
  84. }
  85. } : {};
  86. function read(text) {
  87. var files = [];
  88. angular.forEach(text ? text.split(' ') : [], function(refId) {
  89. // refId is index.html-343, so we need to strip the unique ID when exporting the name
  90. files.push({name: refId.replace(/-\d+$/, ''), content: getEmbeddedTemplate(refId)});
  91. });
  92. return files;
  93. }
  94. };
  95. docsApp.serviceFactory.loadedUrls = function($document) {
  96. var urls = {};
  97. angular.forEach($document.find('script'), function(script) {
  98. var match = script.src.match(/^.*\/([^\/]*\.js)$/);
  99. if (match) {
  100. urls[match[1].replace(/(\-\d.*)?(\.min)?\.js$/, '.js')] = match[0];
  101. }
  102. });
  103. urls.base = [];
  104. angular.forEach(NG_DOCS.scripts, function(script) {
  105. var match = urls[script.replace(/(\-\d.*)?(\.min)?\.js$/, '.js')];
  106. if (match) {
  107. urls.base.push(match);
  108. }
  109. });
  110. return urls;
  111. };
  112. docsApp.serviceFactory.formPostData = function($document) {
  113. return function(url, fields) {
  114. var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="_blank"></form>');
  115. angular.forEach(fields, function(value, name) {
  116. var input = angular.element('<input type="hidden" name="' + name + '">');
  117. input.attr('value', value);
  118. form.append(input);
  119. });
  120. $document.find('body').append(form);
  121. form[0].submit();
  122. form.remove();
  123. };
  124. };
  125. docsApp.serviceFactory.openPlunkr = function(templateMerge, formPostData, loadedUrls) {
  126. return function(content) {
  127. var allFiles = [].concat(content.js, content.css, content.html);
  128. var indexHtmlContent = '<!doctype html>\n' +
  129. '<html ng-app="{{module}}">\n' +
  130. ' <head>\n' +
  131. '{{scriptDeps}}' +
  132. ' </head>\n' +
  133. ' <body>\n\n' +
  134. '{{indexContents}}\n\n' +
  135. ' </body>\n' +
  136. '</html>\n';
  137. var scriptDeps = '';
  138. angular.forEach(loadedUrls.base, function(url) {
  139. scriptDeps += ' <script src="' + url + '"></script>\n';
  140. });
  141. angular.forEach(allFiles, function(file) {
  142. var ext = file.name.split(/\./).pop();
  143. if (ext == 'css') {
  144. scriptDeps += ' <link rel="stylesheet" href="' + file.name + '" type="text/css">\n';
  145. } else if (ext == 'js' && file.name !== 'angular.js') {
  146. scriptDeps += ' <script src="' + file.name + '"></script>\n';
  147. }
  148. });
  149. indexProp = {
  150. module: content.module,
  151. scriptDeps: scriptDeps,
  152. indexContents: content.html[0].content
  153. };
  154. var postData = {};
  155. angular.forEach(allFiles, function(file, index) {
  156. if (file.content && file.name != 'index.html') {
  157. postData['files[' + file.name + ']'] = file.content;
  158. }
  159. });
  160. postData['files[index.html]'] = templateMerge(indexHtmlContent, indexProp);
  161. postData['tags[]'] = "angularjs";
  162. postData.private = true;
  163. postData.description = 'AngularJS Example Plunkr';
  164. formPostData('http://plnkr.co/edit/?p=preview', postData);
  165. };
  166. };
  167. docsApp.serviceFactory.sections = function serviceFactory() {
  168. var sections = {
  169. getPage: function(sectionId, partialId) {
  170. var pages = sections[sectionId];
  171. partialId = partialId || 'index';
  172. for (var i = 0, ii = pages.length; i < ii; i++) {
  173. if (pages[i].id == partialId) {
  174. return pages[i];
  175. }
  176. }
  177. return null;
  178. }
  179. };
  180. angular.forEach(NG_DOCS.pages, function(page) {
  181. var url = page.section + '/' + page.id;
  182. if (page.id == 'angular.Module') {
  183. page.partialUrl = 'partials/api/angular.IModule.html';
  184. } else {
  185. page.partialUrl = 'partials/' + url.replace(':', '.') + '.html';
  186. }
  187. page.url = (NG_DOCS.html5Mode ? '' : '#/') + url;
  188. if (!sections[page.section]) { sections[page.section] = []; }
  189. sections[page.section].push(page);
  190. });
  191. return sections;
  192. };
  193. docsApp.controller.DocsController = function($scope, $location, $window, sections) {
  194. var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/,
  195. GLOBALS = /^angular\.([^\.]+)$/,
  196. MODULE = /^([^\.]+)$/,
  197. MODULE_MOCK = /^angular\.mock\.([^\.]+)$/,
  198. MODULE_CONTROLLER = /^(.+)\.controllers?:([^\.]+)$/,
  199. MODULE_DIRECTIVE = /^(.+)\.directives?:([^\.]+)$/,
  200. MODULE_DIRECTIVE_INPUT = /^(.+)\.directives?:input\.([^\.]+)$/,
  201. MODULE_FILTER = /^(.+)\.filters?:([^\.]+)$/,
  202. MODULE_CUSTOM = /^(.+)\.([^\.]+):([^\.]+)$/,
  203. MODULE_SERVICE = /^(.+)\.([^\.]+?)(Provider)?$/,
  204. MODULE_TYPE = /^([^\.]+)\..+\.([A-Z][^\.]+)$/;
  205. /**********************************
  206. Publish methods
  207. ***********************************/
  208. $scope.navClass = function(page1, page2) {
  209. return {
  210. first: this.$first,
  211. last: this.$last,
  212. active: page1 && this.currentPage == page1 || page2 && this.currentPage == page2,
  213. match: this.focused && this.currentPage != page1 &&
  214. this.bestMatch.rank > 0 && this.bestMatch.page == page1
  215. };
  216. };
  217. $scope.isActivePath = function(url) {
  218. if (url.charAt(0) == '#') {
  219. url = url.substring(1, url.length);
  220. }
  221. return $location.path().indexOf(url) > -1;
  222. };
  223. $scope.submitForm = function() {
  224. if ($scope.bestMatch) {
  225. var url = $scope.bestMatch.page.url;
  226. $location.path(NG_DOCS.html5Mode ? url : url.substring(1));
  227. }
  228. };
  229. $scope.afterPartialLoaded = function() {
  230. var currentPageId = $location.path();
  231. $scope.partialTitle = $scope.currentPage.shortName;
  232. $window._gaq && $window._gaq.push(['_trackPageview', currentPageId]);
  233. loadDisqus(currentPageId);
  234. };
  235. /**********************************
  236. Watches
  237. ***********************************/
  238. $scope.sections = {};
  239. angular.forEach(NG_DOCS.sections, function(section, url) {
  240. $scope.sections[(NG_DOCS.html5Mode ? '' : '#/') + url] = section;
  241. });
  242. $scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) {
  243. var parts = path.split('/'),
  244. sectionId = parts[1],
  245. partialId = parts[2],
  246. page, sectionName = $scope.sections[(NG_DOCS.html5Mode ? '' : '#/') + sectionId];
  247. if (!sectionName) { return; }
  248. $scope.currentPage = page = sections.getPage(sectionId, partialId);
  249. if (!$scope.currentPage) {
  250. $scope.partialTitle = 'Error: Page Not Found!';
  251. page = {};
  252. }
  253. updateSearch();
  254. // Update breadcrumbs
  255. var breadcrumb = $scope.breadcrumb = [],
  256. match, sectionPath = (NG_DOCS.html5Mode ? '' : '#/') + sectionId;
  257. if (partialId) {
  258. breadcrumb.push({ name: sectionName, url: sectionPath });
  259. if (partialId == 'angular.Module') {
  260. breadcrumb.push({ name: 'angular.Module' });
  261. } else if (match = partialId.match(GLOBALS)) {
  262. breadcrumb.push({ name: partialId });
  263. } else if (match = partialId.match(MODULE)) {
  264. match[1] = page.moduleName || match[1];
  265. breadcrumb.push({ name: match[1] });
  266. } else if (match = partialId.match(MODULE_FILTER)) {
  267. match[1] = page.moduleName || match[1];
  268. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  269. breadcrumb.push({ name: match[2] });
  270. } else if (match = partialId.match(MODULE_CONTROLLER)) {
  271. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  272. breadcrumb.push({ name: match[2] });
  273. } else if (match = partialId.match(MODULE_DIRECTIVE)) {
  274. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  275. breadcrumb.push({ name: match[2] });
  276. } else if (match = partialId.match(MODULE_DIRECTIVE_INPUT)) {
  277. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  278. breadcrumb.push({ name: 'input' });
  279. breadcrumb.push({ name: match[2] });
  280. } else if (match = partialId.match(MODULE_CUSTOM)) {
  281. match[1] = page.moduleName || match[1];
  282. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  283. breadcrumb.push({ name: match[3] });
  284. } else if (match = partialId.match(MODULE_TYPE)) {
  285. match[1] = page.moduleName || match[1];
  286. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  287. breadcrumb.push({ name: match[2] });
  288. } else if (match = partialId.match(MODULE_SERVICE)) {
  289. if ( page.type === 'overview') {
  290. // module name with dots looks like a service
  291. breadcrumb.push({ name: partialId });
  292. } else {
  293. match[1] = page.moduleName || match[1];
  294. breadcrumb.push({ name: match[1], url: sectionPath + '/' + match[1] });
  295. breadcrumb.push({ name: match[2] + (match[3] || '') });
  296. }
  297. } else if (match = partialId.match(MODULE_MOCK)) {
  298. breadcrumb.push({ name: 'angular.mock.' + match[1] });
  299. } else {
  300. breadcrumb.push({ name: page.shortName });
  301. }
  302. } else {
  303. breadcrumb.push({ name: sectionName });
  304. }
  305. });
  306. $scope.$watch('search', updateSearch);
  307. /**********************************
  308. Initialize
  309. ***********************************/
  310. $scope.versionNumber = angular.version.full;
  311. $scope.version = angular.version.full + " " + angular.version.codeName;
  312. $scope.futurePartialTitle = null;
  313. $scope.loading = 0;
  314. if (!$location.path() || INDEX_PATH.test($location.path())) {
  315. $location.path(NG_DOCS.startPage).replace();
  316. }
  317. /**********************************
  318. Private methods
  319. ***********************************/
  320. function updateSearch() {
  321. var cache = {},
  322. pages = sections[$location.path().split('/')[1]],
  323. modules = $scope.modules = [],
  324. otherPages = $scope.pages = [],
  325. search = $scope.search,
  326. bestMatch = {page: null, rank:0};
  327. angular.forEach(pages, function(page) {
  328. var match,
  329. id = page.id,
  330. section = page.section;
  331. if (!(match = rank(page, search))) return;
  332. if (match.rank > bestMatch.rank) {
  333. bestMatch = match;
  334. }
  335. if (page.id == 'index') {
  336. //skip
  337. } else if (!NG_DOCS.apis[section]) {
  338. otherPages.push(page);
  339. } else if (id == 'angular.Module') {
  340. module('ng', section).types.push(page);
  341. } else if (match = id.match(GLOBALS)) {
  342. module('ng', section).globals.push(page);
  343. } else if (match = id.match(MODULE)) {
  344. module(page.moduleName || match[1], section);
  345. } else if (match = id.match(MODULE_FILTER)) {
  346. module(page.moduleName || match[1], section).filters.push(page);
  347. } else if (match = id.match(MODULE_CONTROLLER) && page.type === 'controller') {
  348. module(page.moduleName || match[1], section).controllers.push(page);
  349. } else if (match = id.match(MODULE_DIRECTIVE)) {
  350. module(page.moduleName || match[1], section).directives.push(page);
  351. } else if (match = id.match(MODULE_DIRECTIVE_INPUT)) {
  352. module(page.moduleName || match[1], section).directives.push(page);
  353. } else if (match = id.match(MODULE_CUSTOM)) {
  354. if (page.type === 'service') {
  355. module(page.moduleName || match[1], section).service(match[3])[page.id.match(/^.+Provider$/) ? 'provider' : 'instance'] = page;
  356. } else {
  357. var m = module(page.moduleName || match[1], section),
  358. listName = page.type + 's';
  359. if (m[listName]) {
  360. m[listName].push(page);
  361. } else {
  362. m.others.push(page);
  363. }
  364. }
  365. } else if (match = id.match(MODULE_TYPE) && page.type === 'type') {
  366. module(page.moduleName || match[1], section).types.push(page);
  367. } else if (match = id.match(MODULE_SERVICE)) {
  368. if (page.type === 'overview') {
  369. module(id, section);
  370. } else {
  371. module(page.moduleName || match[1], section).service(match[2])[match[3] ? 'provider' : 'instance'] = page;
  372. }
  373. } else if (match = id.match(MODULE_MOCK)) {
  374. module('ngMock', section).globals.push(page);
  375. }
  376. });
  377. $scope.bestMatch = bestMatch;
  378. /*************/
  379. function module(name, section) {
  380. var module = cache[name];
  381. if (!module) {
  382. module = cache[name] = {
  383. name: name,
  384. url: (NG_DOCS.html5Mode ? '' : '#/') + section + '/' + name,
  385. globals: [],
  386. controllers: [],
  387. directives: [],
  388. services: [],
  389. others: [],
  390. service: function(name) {
  391. var service = cache[this.name + ':' + name];
  392. if (!service) {
  393. service = {name: name};
  394. cache[this.name + ':' + name] = service;
  395. this.services.push(service);
  396. }
  397. return service;
  398. },
  399. types: [],
  400. filters: []
  401. };
  402. modules.push(module);
  403. }
  404. return module;
  405. }
  406. function rank(page, terms) {
  407. var ranking = {page: page, rank:0},
  408. keywords = page.keywords,
  409. title = page.shortName.toLowerCase();
  410. terms && angular.forEach(terms.toLowerCase().split(' '), function(term) {
  411. var index;
  412. if (ranking) {
  413. if (keywords.indexOf(term) == -1) {
  414. ranking = null;
  415. } else {
  416. ranking.rank ++; // one point for each term found
  417. if ((index = title.indexOf(term)) != -1) {
  418. ranking.rank += 20 - index; // ten points if you match title
  419. }
  420. }
  421. }
  422. });
  423. return ranking;
  424. }
  425. }
  426. function loadDisqus(currentPageId) {
  427. if (!NG_DOCS.discussions) { return; }
  428. // http://docs.disqus.com/help/2/
  429. window.disqus_shortname = NG_DOCS.discussions.shortName;
  430. window.disqus_identifier = currentPageId;
  431. window.disqus_url = NG_DOCS.discussions.url + currentPageId;
  432. window.disqus_developer = NG_DOCS.discussions.dev;
  433. // http://docs.disqus.com/developers/universal/
  434. (function() {
  435. var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
  436. dsq.src = '//angularjs.disqus.com/embed.js';
  437. (document.getElementsByTagName('head')[0] ||
  438. document.getElementsByTagName('body')[0]).appendChild(dsq);
  439. })();
  440. angular.element(document.getElementById('disqus_thread')).html('');
  441. }
  442. };
  443. function module(name, modules, optional) {
  444. if (optional) {
  445. angular.forEach(optional, function(name) {
  446. try {
  447. angular.module(name);
  448. modules.push(name);
  449. } catch(e) {}
  450. });
  451. }
  452. return angular.module(name, modules);
  453. }
  454. module('docsApp', ['bootstrap', 'bootstrapPrettify'], ['ngAnimate']).
  455. config(function($locationProvider) {
  456. if (NG_DOCS.html5Mode) {
  457. $locationProvider.html5Mode(true).hashPrefix('!');
  458. }
  459. }).
  460. factory(docsApp.serviceFactory).
  461. directive(docsApp.directive).
  462. controller(docsApp.controller);