misago.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. /* global -Misago */
  2. /* exported Misago */
  3. (function () {
  4. 'use strict';
  5. window.Misago = function() {
  6. var ns = Object.getPrototypeOf(this);
  7. var self = this;
  8. // Context data
  9. this.context = {
  10. // Empty settings
  11. SETTINGS: {}
  12. };
  13. // Services
  14. this._services = [];
  15. this.addService = function(name, factory, order) {
  16. this._services.push({
  17. name: name,
  18. item: factory,
  19. after: this.get(order, 'after'),
  20. before: this.get(order, 'before')
  21. });
  22. };
  23. this._initServices = function(services) {
  24. var ordered_services = new ns.OrderedList(services).order(false);
  25. ordered_services.forEach(function (item) {
  26. var factory = null;
  27. if (item.item.factory !== undefined) {
  28. factory = item.item.factory;
  29. } else {
  30. factory = item.item;
  31. }
  32. var service_instance = factory(self);
  33. if (service_instance) {
  34. self[item.name] = service_instance;
  35. }
  36. });
  37. };
  38. this._destroyServices = function(services) {
  39. var ordered_services = new ns.OrderedList(services).order();
  40. ordered_services.reverse();
  41. ordered_services.forEach(function (item) {
  42. if (item.destroy !== undefined) {
  43. item.destroy(self);
  44. }
  45. });
  46. };
  47. this.registerCoreServices = function() {
  48. this.addService('conf', ns.Conf);
  49. this.addService('component', ns.ComponentFactory);
  50. this.addService('router', ns.RouterFactory);
  51. this.addService('api', ns.Api);
  52. this.addService('outlet', ns.Outlet);
  53. this.addService('title', ns.PageTitle);
  54. this.addService('start-routing', ns.startRouting);
  55. };
  56. // App init/destory
  57. this.setup = false;
  58. this.init = function(setup) {
  59. this.setup = {
  60. fixture: ns.get(setup, 'fixture', null),
  61. in_test: ns.get(setup, 'in_test', false)
  62. };
  63. this._initServices(this._services);
  64. };
  65. this.destroy = function() {
  66. this._destroyServices();
  67. };
  68. };
  69. }());
  70. (function (ns) {
  71. 'use strict';
  72. var persistent = function(el, isInit, context) {
  73. context.retain = true;
  74. };
  75. ns.ForumLayout = {
  76. view: function(ctrl, _) {
  77. return [
  78. _.component(ns.ForumNavbar),
  79. m('#router-fixture', {config: persistent}),
  80. _.component(ns.ForumFooter)
  81. ];
  82. }
  83. };
  84. }(Misago.prototype));
  85. (function (ns) {
  86. 'use strict';
  87. var self = {
  88. controller: function() {
  89. var _ = self.container;
  90. _.setTitle(_.settings.forum_index_title);
  91. },
  92. view: function() {
  93. return m('.container', [
  94. m('h1', 'Forum index page!'),
  95. m('p', 'Lorem ipsum dolor met sit amet elit.'),
  96. m('p', 'Sequar elit dolor nihi putto.')
  97. ]);
  98. }
  99. };
  100. ns.IndexPage = self;
  101. }(Misago.prototype));
  102. (function (ns) {
  103. 'use strict';
  104. var legalPageFactory = function(type_name, default_title) {
  105. var dashed_type_name = type_name.replace(/_/g, '-');
  106. var self = {
  107. is_destroyed: true,
  108. controller: function() {
  109. var _ = self.container;
  110. self.is_destroyed = false;
  111. if (ns.get(_.settings, type_name + '_link')) {
  112. window.location = ns.get(_.settings, type_name + '_link');
  113. } else {
  114. self.vm.init(_);
  115. }
  116. return {
  117. onunload: function() {
  118. self.is_destroyed = true;
  119. }
  120. };
  121. },
  122. vm: {
  123. is_busy: false,
  124. is_ready: false,
  125. content: null,
  126. init: function(_) {
  127. var vm = this;
  128. if (vm.is_ready) {
  129. _.setTitle(vm.title);
  130. } else {
  131. _.setTitle();
  132. if (!vm.is_busy) {
  133. vm.is_busy = true;
  134. _.api.one('legal-pages', dashed_type_name).then(function(data) {
  135. vm.title = data.title || default_title;
  136. vm.body = data.body;
  137. vm.is_busy = false;
  138. vm.is_ready = true;
  139. if (!self.is_destroyed) {
  140. _.setTitle(vm.title);
  141. m.redraw();
  142. }
  143. });
  144. }
  145. }
  146. }
  147. },
  148. view: function() {
  149. var _ = this.container;
  150. if (this.vm.is_ready) {
  151. return m('.page.page-legal.page-legal-' + dashed_type_name, [
  152. _.component(ns.PageHeader, {title: this.vm.title}),
  153. m('.container',
  154. m.trust(this.vm.body)
  155. )
  156. ]);
  157. } else {
  158. return _.component(ns.LoadingPage);
  159. }
  160. }
  161. };
  162. return self;
  163. };
  164. ns.TermsOfServicePage = legalPageFactory(
  165. 'terms_of_service', gettext('Terms of service'));
  166. ns.PrivacyPolicyPage = legalPageFactory(
  167. 'privacy_policy', gettext('Privacy policy'));
  168. }(Misago.prototype));
  169. (function (ns) {
  170. 'use strict';
  171. ns.Loader = {
  172. view: function() {
  173. return m('.loader.sk-folding-cube', [
  174. m('.sk-cube1.sk-cube'),
  175. m('.sk-cube2.sk-cube'),
  176. m('.sk-cube4.sk-cube'),
  177. m('.sk-cube3.sk-cube')
  178. ]);
  179. }
  180. };
  181. ns.LoadingPage = {
  182. view: function(ctrl, _) {
  183. return m('.page.page-loading',
  184. _.component(ns.Loader)
  185. );
  186. }
  187. };
  188. } (Misago.prototype));
  189. (function (ns) {
  190. 'use strict';
  191. var setupMarkup = function(el, isInit, context) {
  192. context.retain = true;
  193. };
  194. ns.MisagoMarkup = {
  195. view: function(ctrl, content) {
  196. return m('article.misago-markup', {config: setupMarkup}, m.trust(content));
  197. }
  198. };
  199. }(Misago.prototype));
  200. (function (ns) {
  201. 'use strict';
  202. ns.PageHeader = {
  203. view: function(ctrl, options) {
  204. return m('.page-header',
  205. m('.container', [
  206. m('h1', options.title),
  207. ])
  208. );
  209. }
  210. };
  211. }(Misago.prototype));
  212. (function (ns) {
  213. 'use strict';
  214. var legalLink = function(_, legal_type, default_title) {
  215. var url = ns.get(_.settings, legal_type + '_link');
  216. if (!url && ns.get(_.settings, legal_type)) {
  217. url = _.router.url(legal_type);
  218. }
  219. if (url) {
  220. return m('li',
  221. m('a', {href: url}, ns.get(_.settings, legal_type + '_title', default_title))
  222. );
  223. } else {
  224. return null;
  225. }
  226. };
  227. ns.FooterNav = {
  228. isVisible: function(settings) {
  229. return [
  230. !!settings.forum_footnote,
  231. !!settings.terms_of_service,
  232. !!settings.terms_of_service_link,
  233. !!settings.privacy_policy,
  234. !!settings.privacy_policy_link
  235. ].indexOf(true) !== -1;
  236. },
  237. view: function(ctrl, _) {
  238. var items = [];
  239. if (_.settings.forum_footnote) {
  240. items.push(m('li.forum-footnote', m.trust(_.settings.forum_footnote)));
  241. }
  242. items.push(legalLink(_, 'terms_of_service', gettext('Terms of service')));
  243. items.push(legalLink(_, 'privacy_policy', gettext('Privacy policy')));
  244. return m('ul.list-inline.footer-nav', items);
  245. }
  246. };
  247. }(Misago.prototype));
  248. (function (ns) {
  249. 'use strict';
  250. ns.ForumFooter = {
  251. view: function(ctrl, _) {
  252. var nav = null;
  253. if (ns.FooterNav.isVisible(_.settings)) {
  254. nav = _.component(ns.FooterNav);
  255. }
  256. return m('footer.forum-footer', [
  257. m('.container',
  258. m('.footer-content', [
  259. nav,
  260. _.component(ns.FooterMisagoBranding)
  261. ])
  262. )
  263. ]);
  264. }
  265. };
  266. }(Misago.prototype));
  267. (function (ns) {
  268. 'use strict';
  269. ns.FooterMisagoBranding = {
  270. view: function() {
  271. return m('a.misago-branding[href=http://misago-project.org]', [
  272. "powered by ", m('strong', "misago")
  273. ]);
  274. }
  275. };
  276. }(Misago.prototype));
  277. (function (ns) {
  278. 'use strict';
  279. ns.BrandFull = {
  280. view: function(ctrl, branding, _) {
  281. var children = [
  282. m('img', {
  283. src: _.router.staticUrl('misago/img/site-logo.png'),
  284. alt: _.settings.forum_name
  285. })
  286. ];
  287. if (branding) {
  288. children.push(branding);
  289. }
  290. return m('a.navbar-brand', {href: _.router.url('index')}, children);
  291. }
  292. };
  293. }(Misago.prototype));
  294. (function (ns) {
  295. 'use strict';
  296. ns.ForumNavbar = {
  297. view: function(ctrl, _) {
  298. var desktop_navbar = [];
  299. if (_.settings.forum_branding_display) {
  300. desktop_navbar.push(_.component(ns.BrandFull, _.settings.forum_branding_text));
  301. }
  302. desktop_navbar.push(m('ul.nav.navbar-nav', [
  303. m('li', m("a", {config: m.route, href: _.router.url('index')}, 'Index')),
  304. m('li', m("a", {config: m.route, href: _.router.url('test')}, 'Test'))
  305. ]));
  306. return m('nav.navbar.navbar-default.navbar-static-top[role="navigation"]', [
  307. m('.container.navbar-full.hidden-xs.hidden-sm', desktop_navbar)
  308. ]);
  309. }
  310. };
  311. }(Misago.prototype));
  312. (function (ns) {
  313. 'use strict';
  314. var Api = function(_) {
  315. // Ajax implementation
  316. var cookie_regex = new RegExp(_.context.CSRF_COOKIE_NAME + '\=([^;]*)');
  317. this.csrf_token = ns.get(document.cookie.match(cookie_regex), 0).split('=')[1];
  318. this.ajax = function(method, url, data, progress) {
  319. var deferred = m.deferred();
  320. var ajax_settings = {
  321. url: url,
  322. method: method,
  323. headers: {
  324. 'X-CSRFToken': this.csrf_token
  325. },
  326. data: data | {},
  327. dataType: 'json',
  328. success: function(data) {
  329. deferred.resolve(data);
  330. },
  331. error: function(jqXHR) {
  332. deferred.reject(jqXHR);
  333. }
  334. };
  335. if (progress) {
  336. return; // not implemented... yet!
  337. }
  338. $.ajax(ajax_settings);
  339. return deferred.promise;
  340. };
  341. this.get = function(url) {
  342. var preloaded_data = ns.pop(_.preloaded_data, url);
  343. if (preloaded_data) {
  344. var deferred = m.deferred();
  345. deferred.resolve(preloaded_data);
  346. return deferred.promise;
  347. } else {
  348. return this.ajax('GET', url);
  349. }
  350. };
  351. this.post = function(url) {
  352. return this.ajax('POST', url);
  353. };
  354. // API
  355. this.buildUrl = function(model, call, querystrings) {
  356. var url = _.router.base_url;
  357. url += 'api/' + model + '/';
  358. return url;
  359. };
  360. this.one = function(model, id) {
  361. var url = this.buildUrl(model) + id + '/';
  362. return this.get(url);
  363. };
  364. this.many = function(model, filters) {
  365. };
  366. this.call = function(model, target, call, data) {
  367. };
  368. };
  369. ns.Api = function(_) {
  370. return new Api(_);
  371. };
  372. }(Misago.prototype));
  373. (function (ns) {
  374. 'use strict';
  375. ns.ComponentFactory = function(_) {
  376. // Component factory
  377. _.component = function() {
  378. var arguments_array = [];
  379. for (var i = 0; i < arguments.length; i += 1) {
  380. arguments_array.push(arguments[i]);
  381. }
  382. arguments_array.push(_);
  383. return m.component.apply(undefined, arguments_array);
  384. };
  385. };
  386. }(Misago.prototype));
  387. (function (ns) {
  388. 'use strict';
  389. ns.Conf = function(_) {
  390. _.settings = ns.get(_.context, 'SETTINGS', {});
  391. };
  392. }(Misago.prototype));
  393. (function (ns) {
  394. 'use strict';
  395. ns.Outlet = {
  396. factory: function(_) {
  397. if (_.setup.fixture) {
  398. m.mount(document.getElementById(_.setup.fixture),
  399. _.component(ns.ForumLayout));
  400. }
  401. },
  402. destroy: function(_) {
  403. if (_.setup.fixture) {
  404. m.mount(_.setup.fixture, null);
  405. }
  406. }
  407. };
  408. }(Misago.prototype));
  409. (function (ns) {
  410. 'use strict';
  411. var Router = function(_) {
  412. var self = this;
  413. this.base_url = $('base').attr('href');
  414. this.static_url = ns.get(_.context, 'STATIC_URL', '/');
  415. this.media_url = ns.get(_.context, 'MEDIA_URL', '/');
  416. // Routing
  417. this.urls = {};
  418. this.reverses = {};
  419. var populatePatterns = function(urlconf) {
  420. urlconf.patterns().forEach(function(url) {
  421. // set service container on component
  422. url.component.container = _;
  423. var final_pattern = self.base_url + url.pattern;
  424. final_pattern = final_pattern.replace('//', '/');
  425. self.urls[final_pattern] = url.component;
  426. self.reverses[url.name] = final_pattern;
  427. });
  428. };
  429. this.startRouting = function(urlconf, fixture) {
  430. populatePatterns(urlconf);
  431. this.fixture = fixture;
  432. m.route.mode = 'pathname';
  433. m.route(fixture, '/', this.urls);
  434. };
  435. this.url = function(name) {
  436. return this.reverses[name];
  437. };
  438. // Delegate clicks
  439. this.delegate_element = null;
  440. this.delegate_name = 'click.misago-router';
  441. this.cleanUrl = function(url) {
  442. if (!url) { return; }
  443. // Is link relative?
  444. var is_relative = url.substr(0, 1) === '/' && url.substr(0, 2) !== '//';
  445. // If link contains host, validate to see if its outgoing
  446. if (!is_relative) {
  447. var location = window.location;
  448. // If protocol matches current one, strip it from string
  449. // otherwhise stop handler
  450. if (url.substr(0, 2) !== '//') {
  451. var protocol = url.substr(0, location.protocol.length + 2);
  452. if (protocol !== location.protocol + '//') { return; }
  453. url = url.substr(location.protocol.length + 2);
  454. } else {
  455. url = url.substr(2);
  456. }
  457. // Host checks out?
  458. if (url.substr(0, location.host.length) !== location.host) { return; }
  459. url = url.substr(location.host.length);
  460. }
  461. // Is link within Ember app?
  462. if (url.substr(0, this.base_url.length) !== this.base_url) { return; }
  463. // Is link to media/static/avatar server?
  464. if (url.substr(0, this.static_url.length) === this.static_url) { return; }
  465. if (url.substr(0, this.media_url.length) === this.media_url) { return; }
  466. var avatars_url = '/user-avatar/';
  467. if (url.substr(0, avatars_url.length) === avatars_url) { return; }
  468. return url;
  469. };
  470. this.delegateClicks = function(element) {
  471. this.delegate_element = element;
  472. $(this.delegate_element).on(this.delegate_name, 'a', function(e) {
  473. var clean_url = self.cleanUrl(e.target.href);
  474. if (clean_url) {
  475. if (clean_url != m.route()) {
  476. m.route(clean_url);
  477. }
  478. e.preventDefault();
  479. }
  480. });
  481. };
  482. this.destroy = function() {
  483. $(this.delegate_element).off(this.delegate_name);
  484. };
  485. // Media/Static url
  486. var prefixUrl = function(prefix) {
  487. return function(url) {
  488. return prefix + url;
  489. };
  490. };
  491. this.staticUrl = prefixUrl(this.static_url);
  492. this.mediaUrl = prefixUrl(this.media_url);
  493. };
  494. ns.RouterFactory = function(_) {
  495. return new Router(_);
  496. };
  497. ns.startRouting = function(_) {
  498. _.router.startRouting(ns.urls, document.getElementById('router-fixture'));
  499. _.router.delegateClicks(document.getElementById(_.setup.fixture));
  500. };
  501. }(Misago.prototype));
  502. (function (ns) {
  503. 'use strict';
  504. ns.PageTitle = function(_) {
  505. _._setTitle = function(title) {
  506. if (typeof title === 'string') {
  507. title = {title: title};
  508. }
  509. var complete_title = title.title;
  510. if (typeof title.page !== 'undefined' && title.page > 1) {
  511. complete_title += ' (' + interpolate(gettext('page %(page)s'), { page:title.page }, true) + ')';
  512. }
  513. if (typeof title.parent !== 'undefined') {
  514. complete_title += ' | ' + title.parent;
  515. }
  516. document.title = complete_title + ' | ' + this.settings.forum_name;
  517. };
  518. _.setTitle = function(title) {
  519. if (title) {
  520. this._setTitle(title);
  521. } else {
  522. document.title = this.settings.forum_name;
  523. }
  524. };
  525. };
  526. }(Misago.prototype));
  527. (function (ns) {
  528. 'use strict';
  529. ns.has = function(obj, key) {
  530. if (obj !== undefined) {
  531. return obj.hasOwnProperty(key);
  532. } else {
  533. return false;
  534. }
  535. };
  536. ns.get = function(obj, key, value) {
  537. if (ns.has(obj, key)) {
  538. return obj[key];
  539. } else if (value !== undefined) {
  540. return value;
  541. } else {
  542. return undefined;
  543. }
  544. };
  545. ns.pop = function(obj, key, value) {
  546. var returnValue = ns.get(obj, key, value);
  547. if (ns.has(obj, key)) {
  548. delete obj[key];
  549. }
  550. return returnValue;
  551. };
  552. }(Misago.prototype));
  553. (function (ns) {
  554. 'use strict';
  555. ns.OrderedList = function(items) {
  556. this.is_ordered = false;
  557. this._items = items || [];
  558. this.add = function(key, item, order) {
  559. this._items.push({
  560. key: key,
  561. item: item,
  562. after: ns.get(order, 'after'),
  563. before: ns.get(order, 'before')
  564. });
  565. };
  566. this.get = function(key, value) {
  567. for (var i = 0; i < this._items.length; i++) {
  568. if (this._items[i].key === key) {
  569. return this._items[i].item;
  570. }
  571. }
  572. return value;
  573. };
  574. this.has = function(key) {
  575. return this.get(key) !== undefined;
  576. };
  577. this.values = function() {
  578. var values = [];
  579. for (var i = 0; i < this._items.length; i++) {
  580. values.push(this._items[i].item);
  581. }
  582. return values;
  583. };
  584. this.order = function(values_only) {
  585. if (!this.is_ordered) {
  586. this._items = this._order(this._items);
  587. this.is_ordered = true;
  588. }
  589. if (values_only || typeof values_only === 'undefined') {
  590. return this.values();
  591. } else {
  592. return this._items;
  593. }
  594. };
  595. this._order = function(unordered) {
  596. // Index of unordered items
  597. var index = [];
  598. unordered.forEach(function (item) {
  599. index.push(item.key);
  600. });
  601. // Ordered items
  602. var ordered = [];
  603. var ordering = [];
  604. // First pass: register items that
  605. // don't specify their order
  606. unordered.forEach(function (item) {
  607. if (!item.after && !item.before) {
  608. ordered.push(item);
  609. ordering.push(item.key);
  610. }
  611. });
  612. // Second pass: keep iterating items
  613. // until we hit iterations limit or finish
  614. // ordering list
  615. function insertItem(item) {
  616. var insertAt = -1;
  617. if (ordering.indexOf(item.key) === -1) {
  618. if (item.after) {
  619. insertAt = ordering.indexOf(item.after);
  620. if (insertAt !== -1) {
  621. insertAt += 1;
  622. }
  623. } else if (item.before) {
  624. insertAt = ordering.indexOf(item.before);
  625. }
  626. if (insertAt !== -1) {
  627. ordered.splice(insertAt, 0, item);
  628. ordering.splice(insertAt, 0, item.key);
  629. }
  630. }
  631. }
  632. var iterations = 200;
  633. while (iterations > 0 && index.length !== ordering.length) {
  634. iterations -= 1;
  635. unordered.forEach(insertItem);
  636. }
  637. return ordered;
  638. };
  639. };
  640. } (Misago.prototype));
  641. (function (ns) {
  642. 'use strict';
  643. ns.startsWith = function(string, beginning) {
  644. return string.indexOf(beginning) === 0;
  645. };
  646. ns.endsWith = function(string, tail) {
  647. return string.indexOf(tail, string.length - tail.length) !== -1;
  648. };
  649. }(Misago.prototype));
  650. (function (ns) {
  651. 'use strict';
  652. ns.UrlConfInvalidComponentError = function() {
  653. this.message = 'component argument should be array or object';
  654. };
  655. ns.UrlConf = function() {
  656. var self = this;
  657. this._patterns = [];
  658. this.patterns = function() {
  659. return this._patterns;
  660. };
  661. var prefixPattern = function(prefix, pattern) {
  662. return (prefix + pattern).replace('//', '/');
  663. };
  664. var include = function(prefix, patterns) {
  665. for (var i = 0; i < patterns.length; i ++) {
  666. self.url(prefixPattern(prefix, patterns[i].pattern),
  667. patterns[i].component,
  668. patterns[i].name);
  669. }
  670. };
  671. this.url = function(pattern, component, name) {
  672. if (typeof component !== 'object') {
  673. throw new ns.UrlConfInvalidComponentError();
  674. }
  675. if (pattern === '') {
  676. pattern = '/';
  677. }
  678. if (component instanceof ns.UrlConf) {
  679. include(pattern, component.patterns());
  680. } else {
  681. this._patterns.push({
  682. pattern: pattern,
  683. component: component,
  684. name: name
  685. });
  686. }
  687. };
  688. };
  689. } (Misago.prototype));
  690. (function (ns) {
  691. 'use strict';
  692. ns.loadingPage = function(_) {
  693. return m('.page.page-loading', _.component(ns.Loader));
  694. };
  695. } (Misago.prototype));
  696. (function (ns, UrlConf) {
  697. 'use strict';
  698. var urls = new UrlConf();
  699. urls.url('/', ns.IndexPage, 'index');
  700. // Legal pages
  701. urls.url('/terms-of-service/', ns.TermsOfServicePage, 'terms_of_service');
  702. urls.url('/privacy-policy/', ns.PrivacyPolicyPage, 'privacy_policy');
  703. ns.urls = urls;
  704. } (Misago.prototype, Misago.prototype.UrlConf));