PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/t/upfront/scripts/upfront/upfront-views-editor/fonts.js

https://bitbucket.org/matthewselby/wpdev
JavaScript | 1041 lines | 805 code | 117 blank | 119 comment | 122 complexity | 454b27199bba1742b6e0a24649eb7fbb MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0, LGPL-3.0, LGPL-2.1, AGPL-1.0, BSD-3-Clause, MIT, GPL-3.0, MPL-2.0-no-copyleft-exception
  1. (function($) {
  2. var l10n = Upfront.Settings && Upfront.Settings.l10n
  3. ? Upfront.Settings.l10n.global.views
  4. : Upfront.mainData.l10n.global.views
  5. ;
  6. define([
  7. "text!upfront/templates/popup.html",
  8. 'scripts/upfront/upfront-views-editor/fields'
  9. ], function(popup_tpl, Fields) {
  10. var Variant_View = Backbone.View.extend({
  11. initialize: function(options){
  12. this.options = options || {};
  13. },
  14. className: function() {
  15. var className = 'font-variant-preview';
  16. if (this.model.get('already_added')) {
  17. className += ' font-variant-already-added';
  18. }
  19. return className;
  20. },
  21. template: _.template('<span class="font-family">{{family}} — {{name}}</span>{[ if(already_added) { ]} <span class="already-added">' + l10n.already_added + '</span>{[ } ]}' +
  22. '{[ if(heading_preview) { ]}<h1 style="font-family: {{family}}; font-weight: {{weight}}; font-style: {{style}};" class="heading-font-preview font-preview">' + l10n.header_preview_quote + '</h1>{[ } else { ]}' +
  23. '<p style="font-family: {{family}}; font-weight: {{weight}}; font-style: {{style}};" class="paragraph-font-preview font-preview">' + l10n.body_preview_quote + '</p>{[ } ]}'),
  24. events: {
  25. 'click': 'on_click'
  26. },
  27. render: function() {
  28. this.$el.html(this.template(_.extend({heading_preview: this.options.heading_preview}, this.model.toJSON())));
  29. return this;
  30. },
  31. on_click: function() {
  32. if (this.model.get('already_added')) return;
  33. this.model.set({selected: !this.model.get('selected')});
  34. this.$el.toggleClass('font-variant-selected');
  35. }
  36. });
  37. // THEME FONTS START HERE //
  38. var Font_Model = Backbone.Model.extend({}, {
  39. /*
  40. * Parsing variant to get style and weight for font.
  41. * @return Object { style: style, weight: weight }
  42. */
  43. parse_variant: function(variant) {
  44. var parsed_variant;
  45. // For system fonts there are variants in format "{number} {style}" where {number} is
  46. // 100-900 with step of 100, and {style} is "normal", "italic" or "oblique"
  47. //
  48. // Fog google fonts variants can be in format "regular", "italic", "100" to "900",
  49. // "100italic" to "900italic".
  50. //
  51. // From browser font-weight[s] we'll use: 100 to 900, normal.
  52. // From browser font-style we'll use: italic, normal, oblique.
  53. //
  54. // Regular variant means that both font-weight and font-style are normal or not set.
  55. // Always set both style and weight to make everything easier.
  56. // Always use numbers for weight to make everything easier.
  57. if (variant === 'inherit') {
  58. return {
  59. weight: 'inherit',
  60. style: 'inherit'
  61. };
  62. }
  63. // Cover both '100italic' and '100 italic'
  64. if (!_.isUndefined( variant ) && variant.match(/^(\d+) *(normal|italic|oblique)$/)) {
  65. parsed_variant = variant.match(/^(\d+) *(normal|italic|oblique)/);
  66. return {
  67. weight: parsed_variant[1],
  68. style: parsed_variant[2]
  69. };
  70. }
  71. if (variant === 'italic') {
  72. return {
  73. weight: '400',
  74. style: 'italic'
  75. };
  76. }
  77. // Cover 100, 200, 500 etc styles
  78. if ( !_.isUndefined( variant ) && variant.match(/^\d+$/)) {
  79. return {
  80. weight: variant,
  81. style: 'normal'
  82. };
  83. }
  84. // Default return value, also covers "regular" variant
  85. return {
  86. weight: '400',
  87. style: 'normal'
  88. };
  89. },
  90. /*
  91. * Constructs variant from weight and style.
  92. *
  93. * Variant should always be displayed as:
  94. * "{weight} {style}"
  95. * where weight is {number} from 100 to 900 step 100 and {style} is
  96. * "normal", "italic" or "oblique".
  97. * Unless:
  98. * 1. weight is 400(normal) and style is "normal" than variant is "regular".
  99. * 2. weight is 400(normal) and style is "italic" than variant is "italic",
  100. *
  101. * @return String variant
  102. */
  103. get_variant: function(weight, style) {
  104. if (weight === 'inherit' || style === 'inherit') {
  105. return 'inherit';
  106. }
  107. weight = this.normalize_weight(weight);
  108. if (style === '') style = 'normal';
  109. if (weight === '400' && style === 'normal') return 'regular';
  110. if (weight === '400' && style === 'italic') return 'italic';
  111. return weight + ' ' + style;
  112. },
  113. /*
  114. * @see get_variant()
  115. */
  116. normalize_variant: function(variant) {
  117. var parsed_variant = this.parse_variant(variant);
  118. return this.get_variant(parsed_variant.weight, parsed_variant.style);
  119. },
  120. /*
  121. * Convert weight to number for comparison.
  122. */
  123. normalize_weight: function (weight) {
  124. if ( weight == 'normal' || '' === weight ) return 400;
  125. if ( weight == 'lighter' ) return 100; // either 100-300 depend to the available weight
  126. if ( weight == 'bold' ) return 700;
  127. if ( weight == 'bolder' ) return 900; // either 800-900 depend to the available weight
  128. return weight;
  129. },
  130. get_default_variant: function(family) {
  131. return 'inherit';
  132. }
  133. });
  134. var Fonts_Collection = Backbone.Collection.extend({
  135. model: Font_Model
  136. });
  137. /**
  138. * Takes care about Google fonts.
  139. */
  140. var Google_Fonts_Storage = function() {
  141. var fonts = false;
  142. /*
  143. * Returns deferred that resolves to fonts collection containing all Google fonts.
  144. */
  145. var get_fonts = function() {
  146. if (fonts) return fonts;
  147. var request = Upfront.Util.post({action: "upfront_list_google_fonts"});
  148. // We're gonna pipe response since we need to convert it to fonts collection first.
  149. request = request.then(
  150. function(response) {
  151. fonts = new Fonts_Collection(response.data);
  152. // Return collection instead original response
  153. return fonts;
  154. }
  155. );
  156. return request;
  157. };
  158. // Prevent multiple requests from being made while waiting for AJAX response.
  159. this.get_fonts = _.debounce(get_fonts, 100);
  160. };
  161. var google_fonts_storage = new Google_Fonts_Storage();
  162. var System_Fonts_Storage = function() {
  163. var font_families = [
  164. { family: "Andale Mono", category:'monospace' },
  165. { family: "Arial", category:'sans-serif' },
  166. { family: "Arial Black", category:'sans-serif' },
  167. { family: "Courier New", category:'monospace' },
  168. { family: "Georgia", category:'serif' },
  169. { family: "Impact", category:'sans-serif' },
  170. { family: "Times New Roman", category:'serif' },
  171. { family: "Trebuchet MS", category:'sans-serif' },
  172. { family: "Verdana", category:'sans-serif' }
  173. ];
  174. var system_fonts = new Fonts_Collection();
  175. var initialize = function() {
  176. var variants;
  177. // Default variants for system fonts
  178. variants = ['Inherit', '400', '400 italic', '700', '700 italic'];
  179. // Add variants
  180. _.each(font_families, function(font_family) {
  181. font_family.variants = variants;
  182. system_fonts.add(font_family);
  183. });
  184. };
  185. this.get_fonts = function() {
  186. return system_fonts;
  187. };
  188. initialize();
  189. };
  190. var system_fonts_storage = new System_Fonts_Storage();
  191. var ThemeFontModel = Backbone.Model.extend({
  192. initialize: function(attributes) {
  193. this.set({ displayVariant: Font_Model.normalize_variant(attributes.variant) }, { silent: true });
  194. }
  195. });
  196. var ThemeFontsCollection = Backbone.Collection.extend({
  197. model: ThemeFontModel,
  198. get_fonts_for_select: function() {
  199. var typefaces_list = [{ label: l10n.choose_font, value:'' }],
  200. google_fonts = [];
  201. _.each(theme_fonts_collection.models, function(theme_font) {
  202. google_fonts.push(theme_font.get('font').family);
  203. });
  204. _.each(_.uniq(google_fonts), function(google_font) {
  205. typefaces_list.push({label: google_font, value: google_font});
  206. });
  207. _.each(Upfront.mainData.additionalFonts, function(font) {
  208. typefaces_list.push({label: font.family, value: font.family});
  209. });
  210. _.each(system_fonts_storage.get_fonts().models, function(font) {
  211. typefaces_list.push({ label: font.get('family'), value: font.get('family') });
  212. });
  213. return typefaces_list;
  214. },
  215. get_variants: function(font_family) {
  216. var variants;
  217. _.each(system_fonts_storage.get_fonts().models, function(font) {
  218. if (font_family === font.get('family')) {
  219. variants = font.get('variants');
  220. }
  221. });
  222. if (variants) {
  223. return variants;
  224. }
  225. _.each(Upfront.mainData.additionalFonts, function(font) {
  226. if (font_family === font.family) {
  227. variants = ['inherit'].concat(font.variants);
  228. }
  229. });
  230. if (variants) {
  231. return variants;
  232. }
  233. variants = [];
  234. _.each(theme_fonts_collection.models, function(theme_font) {
  235. if (font_family === theme_font.get('font').family) {
  236. variants.push(theme_font.get('displayVariant'));
  237. }
  238. });
  239. variants.unshift('inherit');
  240. return variants;
  241. },
  242. get_variants_for_select: function(font_family) {
  243. var variants;
  244. var typefaces_list = [];
  245. _.each(system_fonts_storage.get_fonts().models, function(font) {
  246. if (font_family === font.get('family')) {
  247. _.each(font.get('variants'), function(font_style) {
  248. typefaces_list.push({ label: font_style, value: font_style });
  249. });
  250. }
  251. });
  252. _.each(Upfront.mainData.additionalFonts, function(font) {
  253. if (font_family === font.family) {
  254. _.each(font.variants, function(font_style) {
  255. typefaces_list.push({ label: font_style, value: font_style });
  256. });
  257. }
  258. });
  259. _.each(theme_fonts_collection.models, function(theme_font) {
  260. if (font_family === theme_font.get('font').family) {
  261. var font_style = theme_font.get('displayVariant');
  262. typefaces_list.push({ label: font_style, value: font_style });
  263. }
  264. });
  265. return typefaces_list;
  266. },
  267. get_additional_font: function(font_family) {
  268. var font = _.findWhere(Upfront.mainData.additionalFonts, {family: font_family});
  269. if (font) return new Backbone.Model(font);
  270. return;
  271. }
  272. });
  273. var theme_fonts_collection = new ThemeFontsCollection(Upfront.mainData.themeFonts);
  274. var IconFont = Backbone.Model.extend({
  275. /**
  276. * Gets the full types collection for icon font
  277. *
  278. * @return {Array}
  279. */
  280. getFullCollectionSet: function () {
  281. return [
  282. 'eot',
  283. 'woff',
  284. //'woff2', // WOFF2 is optional
  285. 'ttf',
  286. 'svg'
  287. ];
  288. },
  289. /**
  290. * Gets the full types collection upload status
  291. *
  292. * @return {Boolean}
  293. */
  294. getUploadStatus: function () {
  295. var full = this.getFullCollectionSet() || [],
  296. current = this.get('files') || {}
  297. ;
  298. return _.keys(current).length >= full.length;
  299. },
  300. /**
  301. * Gets the missing file types message
  302. *
  303. * @return {String}
  304. */
  305. getUploadStatusMessage: function () {
  306. var msg = l10n.icon_fonts_collection_incomplete || '',
  307. current = this.get('files') || {},
  308. missing = []
  309. ;
  310. if (!msg) return '';
  311. _.each(this.getFullCollectionSet(), function (type) {
  312. if (type in current) return true;
  313. missing.push(type);
  314. });
  315. return missing.length
  316. ? msg.replace(/%s/, missing.join(", "))
  317. : ''
  318. ;
  319. }
  320. });
  321. var IconFontCollection = Backbone.Collection.extend({
  322. model: IconFont
  323. });
  324. var icon_fonts_collection = new IconFontCollection(Upfront.mainData.iconFonts);
  325. var Theme_Fonts_Storage = function(stored_fonts) {
  326. var theme_fonts;
  327. var initialize = function() {
  328. // When more than one weights are added at once don't send bunch of server calls
  329. var save_theme_fonts_debounced = _.debounce(save_theme_fonts, 100);
  330. theme_fonts_collection.on('add remove', save_theme_fonts_debounced);
  331. };
  332. var save_theme_fonts = function() {
  333. var postData = {
  334. action: 'upfront_update_theme_fonts',
  335. theme_fonts: theme_fonts_collection.toJSON()
  336. };
  337. Upfront.Util.post(postData)
  338. .error(function(){
  339. return notifier.addMessage(l10n.theme_fonts_save_fail);
  340. });
  341. };
  342. initialize();
  343. };
  344. var theme_fonts_storage = new Theme_Fonts_Storage();
  345. var ThemeFontListItem = Backbone.View.extend({
  346. className: 'theme-font-list-item',
  347. events: {
  348. 'click': 'on_click',
  349. 'click .delete': 'on_delete'
  350. },
  351. template: $(popup_tpl).find('#theme-font-list-item').html(),
  352. render: function() {
  353. this.$el.html(_.template(this.template, {
  354. family: this.model.get('font').family,
  355. variant: this.model.get('displayVariant')
  356. }));
  357. return this;
  358. },
  359. on_click: function() {
  360. this.$el.siblings().removeClass('theme-font-list-item-selected');
  361. this.$el.addClass('theme-font-list-item-selected');
  362. this.trigger('selected', this.model.toJSON());
  363. },
  364. on_delete: function() {
  365. theme_fonts_collection.remove(this.model);
  366. this.remove();
  367. }
  368. });
  369. var ThemeFontsPanel = Backbone.View.extend({
  370. className: 'theme-fonts-panel panel',
  371. template: _.template($(popup_tpl).find('#theme-fonts-panel').html()),
  372. initialize: function(options) {
  373. this.options = options || {};
  374. this.listenTo(this.collection, 'add remove', this.update_stats);
  375. this.listenTo(this.collection, 'add remove', this.render);
  376. },
  377. render: function() {
  378. this.$el.html('');
  379. this.$el.html(this.template({show_no_styles_notice: this.collection.length === 0}));
  380. if (this.collection.length > 0) this.$el.find('.font-list').css('background', 'white');
  381. _.each(this.collection.models, function(model) {
  382. this.add_one(model);
  383. }, this);
  384. this.update_stats();
  385. return this;
  386. },
  387. update_stats: function() {
  388. var msg = l10n.font_styles_selected.replace(/%d/, this.collection.length);
  389. this.$el.find('.font-stats').html('<strong>' + msg + '</strong>');
  390. },
  391. add_one: function(model) {
  392. var themeFontView = new ThemeFontListItem({ model: model });
  393. this.options.parent_view.listenTo(themeFontView, 'selected', this.options.parent_view.replaceFont);
  394. this.$el.find('.font-list').append(themeFontView.render().el);
  395. }
  396. });
  397. var Font_Variants_Preview = Backbone.View.extend({
  398. id: 'font-variants-preview',
  399. initialize: function(options) {
  400. this.options = options || {};
  401. },
  402. addOne: function(model) {
  403. var variant_view = new Variant_View({model: model, heading_preview: this.options.heading_preview});
  404. this.$el.append(variant_view.render().el);
  405. },
  406. render: function() {
  407. _.each(this.collection.models, function(model) {
  408. this.addOne(model);
  409. }, this);
  410. return this;
  411. },
  412. get_selected: function() {
  413. var selected = [];
  414. _.each(this.collection.models, function(model) {
  415. if (model.get('selected')) selected.push(model.get('variant'));
  416. });
  417. return selected;
  418. }
  419. });
  420. var Icon_Fonts_Manager = Backbone.View.extend({
  421. id: 'icon-fonts-manager',
  422. className: 'clearfix',
  423. template: _.template($(popup_tpl).find('#icon-fonts-manager-tpl').html()),
  424. events: {
  425. 'click .upload-icon-font': 'triggerFileChooser',
  426. 'click .icon-font-upload-status': 'triggerFileChooser',
  427. 'click .icon-fonts-list-item': 'makeFontActive',
  428. 'click .icon-fonts-list-item a.expand-toggle': 'expandListItems',
  429. 'click .font-filename a': 'removeFontFile'
  430. },
  431. triggerFileChooser: function (e) {
  432. if (e && e.preventDefault) e.preventDefault();
  433. if (e && e.stopPropagation) e.stopPropagation();
  434. this.$el.find('#upfront-icon-font-input').click();
  435. return false;
  436. },
  437. render: function() {
  438. this.$el.html(this.template({
  439. url: Upfront.mainData.ajax,
  440. show_no_fonts_notice: false,
  441. fonts: this.collection.models
  442. }));
  443. if (_.isUndefined(this.collection.findWhere({active: true}))) {
  444. this.$el.find('[data-family="icomoon"]').addClass('icon-fonts-list-item-active');
  445. }
  446. if (!this.fileUploadInitialized) {
  447. this.fileUploadInitialized = true;
  448. this.initializeFileUpload();
  449. }
  450. return this;
  451. },
  452. /**
  453. * Expand file list items on family item click
  454. *
  455. * @param {Object} e Event
  456. *
  457. * @return {Boolean}
  458. */
  459. expandListItems: function (e) {
  460. if (e && e.preventDefault) e.preventDefault();
  461. if (e && e.stopPropagation) e.stopPropagation();
  462. var $a = $(e.target),
  463. $family = $a.closest(".icon-fonts-list-item")
  464. ;
  465. if ($family.length) $family.toggleClass("expanded");
  466. return false;
  467. },
  468. /**
  469. * Removes the selected font file from the collection
  470. *
  471. * @param {Object} e Event
  472. *
  473. * @return {Boolean}
  474. */
  475. removeFontFile: function (e) {
  476. if (e && e.preventDefault) e.preventDefault();
  477. if (e && e.stopPropagation) e.stopPropagation();
  478. var me = this,
  479. $a = $(e.target),
  480. $font = $a.closest(".font-filename"),
  481. name = $font.attr("data-name"),
  482. idx = $font.attr("data-idx")
  483. ;
  484. if (!name || !idx) return false; // Nothing to do here
  485. Upfront.Util
  486. .post({
  487. action: "upfront_remove_icon_font_file",
  488. name: name,
  489. idx: idx
  490. })
  491. .error(function (data) {
  492. var error = ((data || {}).responseJSON || {}).error || 'Oops, something went wrong';
  493. if (!_.isString(error) && (error || {}).message) error = error.message;
  494. Upfront.Views.Editor.notify(error, 'error');
  495. })
  496. .done(function () {
  497. // Success! This is where we update collection
  498. // and re-render the pane
  499. me.collection.each(function (model) {
  500. var files = model.get("files");
  501. if (files && files[idx] && name === files[idx]) {
  502. delete(files[idx]);
  503. model.set("files", files);
  504. }
  505. });
  506. me.fileUploadInitialized = false;
  507. me.render();
  508. })
  509. ;
  510. return false;
  511. },
  512. initializeFileUpload: function() {
  513. if (!jQuery.fn.fileupload) return false; // No file upload, carry on
  514. var me = this;
  515. this.$el.find('#upfront-upload-icon-font').fileupload({
  516. dataType: 'json',
  517. /**
  518. * Pre-processing handler
  519. *
  520. * Validates submitted file types prior to sending them over
  521. *
  522. * @param {Object} e Event
  523. * @param {Object} data File upload object
  524. */
  525. add: function (e, data) {
  526. if (e.isDefaultPrevented()) {
  527. return false;
  528. }
  529. var allowed = true;
  530. if (data.files && data.files.length) {
  531. _.each(data.files, function (file) {
  532. if (allowed) allowed = !!(file || {}).name.match(/\.(eot|woff|woff2|ttf|svg)$/);
  533. });
  534. }
  535. if (!allowed) return false;
  536. if (data.autoUpload || (data.autoUpload !== false &&
  537. $(this).fileupload('option', 'autoUpload'))) {
  538. data.process().done(function () {
  539. data.submit();
  540. });
  541. }
  542. },
  543. done: function (e, data) {
  544. var font = data.result.data.font;
  545. var fontObject;
  546. if (_.keys(font.files).length === 1) {
  547. me.$el.find('.icon-fonts-list').append('<div data-family="' + font.family + '" class="icon-fonts-list-item">' + font.name + '</div>');
  548. me.collection.add(font);
  549. } else {
  550. fontObject = me.collection.findWhere({'family': font.family});
  551. fontObject.set({files: font.files});
  552. if (fontObject.get('active') === true) {
  553. me.updateActiveFontStyle(font.family);
  554. }
  555. }
  556. me.fileUploadInitialized = false;
  557. me.render();
  558. /*
  559. fontObject = me.collection.findWhere({'family': font.family});
  560. var listItem = me.$el.find('[data-family=' + font.family + ']');
  561. listItem.find('.icon-font-upload-status').remove();
  562. if (fontObject.getUploadStatus() !== true) {
  563. listItem.append('<span class="icon-font-upload-status" title="' + fontObject.getUploadStatusMessage() + '">*</span>');
  564. }
  565. */
  566. },
  567. /**
  568. * Error handler
  569. *
  570. * Notify the user of error when something went wrong
  571. * with font upload as a side-effect
  572. *
  573. * @param {Object} e Event object
  574. * @param {Object} data File uploader object
  575. */
  576. fail: function (e, data) {
  577. var error = (((data || {}).jqXHR || {}).responseJSON || {}).error || 'Oops, something went wrong';
  578. if (!_.isString(error) && (error || {}).message) error = error.message;
  579. Upfront.Views.Editor.notify(error, 'error');
  580. }
  581. });
  582. },
  583. makeFontActive: function(event) {
  584. var fontItem = $(event.currentTarget);
  585. fontItem.siblings().removeClass('icon-fonts-list-item-active');
  586. fontItem.addClass('icon-fonts-list-item-active');
  587. var postData = {
  588. action: 'upfront_update_active_icon_font',
  589. family: fontItem.data('family')
  590. };
  591. Upfront.Util.post(postData)
  592. .error(function(){
  593. return notifier.addMessage('Could not update active icon font');
  594. });
  595. $('#active-icon-font').remove();
  596. _.each(this.collection.models, function(model) {
  597. model.set({'active': false});
  598. });
  599. if (fontItem.data('family') === 'icomoon') {
  600. return; // this is default font, no need to add style for it
  601. }
  602. this.collection.findWhere({family: fontItem.data('family')}).set({active: true});
  603. this.updateActiveFontStyle(fontItem.data('family'));
  604. },
  605. updateActiveFontStyle: function(family) {
  606. var font = this.collection.findWhere({family: family});
  607. var longSrc = '';
  608. _.each(font.get('files'), function(file, type) {
  609. longSrc += "url('" + Upfront.mainData.currentThemeUrl + '/icon-fonts/' + file + "') format('";
  610. switch(type) {
  611. case 'eot':
  612. longSrc += 'embedded-opentype';
  613. break;
  614. case 'woff':
  615. longSrc += 'woff';
  616. break;
  617. case 'woff2':
  618. longSrc += 'woff2';
  619. break;
  620. case 'ttf':
  621. longSrc += 'truetype';
  622. break;
  623. case 'svg':
  624. longSrc += 'svg';
  625. break;
  626. }
  627. longSrc += "'),";
  628. });
  629. var icon_font_style = "@font-face {" +
  630. " font-family: '" + font.get('family') + "';";
  631. if (font.get('files').eot) {
  632. icon_font_style += "src: url('" + Upfront.mainData.currentThemeUrl + '/icon-fonts/' + font.get('files').eot + "');";
  633. }
  634. icon_font_style += " src:" + longSrc.substring(0, longSrc.length - 1) + ';';
  635. icon_font_style +=
  636. " font-weight: normal;" +
  637. " font-style: normal;" +
  638. "}" +
  639. ".uf_font_icon, .uf_font_icon * {" +
  640. " font-family: '" + font.get('family') + "'!important;" +
  641. "}";
  642. $('body').append('<style id="active-icon-font">' + icon_font_style + '</style>');
  643. }
  644. });
  645. var Text_Fonts_Manager = Backbone.View.extend({
  646. id: 'text-fonts-manager',
  647. className: 'clearfix',
  648. template: _.template($(popup_tpl).find('#text-fonts-manager-tpl').html()),
  649. events: {
  650. 'click .add-font-button': 'add_font',
  651. 'click .preview-size-p': 'on_p_click',
  652. 'click .preview-size-h1': 'on_h1_click'
  653. },
  654. initialize: function() {
  655. this.theme_fonts_panel = new ThemeFontsPanel({
  656. collection: this.collection,
  657. parent_view: this
  658. });
  659. this.listenTo(this.collection, 'remove', this.update_variants_on_remove);
  660. },
  661. render: function() {
  662. var me = this;
  663. this.$el.html(this.template({show_no_styles_notice: this.collection.length === 0}));
  664. $.when(google_fonts_storage.get_fonts()).done(function(fonts_collection) {
  665. me.load_google_fonts(fonts_collection);
  666. });
  667. this.$el.find('.add-font-panel').after(this.theme_fonts_panel.render().el);
  668. if (!Upfront.mainData.userDoneFontsIntro) this.$el.addClass('no-styles');
  669. this.$el.find('.choose-font').after('<div class="preview-type"><span class="preview-type-title">' + Upfront.Settings.l10n.global.behaviors.preview_size + '</span><span class="preview-size-p selected-preview-size">P</span><span class="preview-size-h1">H1</span></div>');
  670. return this;
  671. },
  672. on_p_click: function() {
  673. this.$el.find('.preview-size-h1').removeClass('selected-preview-size');
  674. this.$el.find('.preview-size-p').addClass('selected-preview-size');
  675. this.heading_preview = false;
  676. this.update_variants();
  677. },
  678. on_h1_click: function() {
  679. this.$el.find('.preview-size-h1').addClass('selected-preview-size');
  680. this.$el.find('.preview-size-p').removeClass('selected-preview-size');
  681. this.heading_preview = true;
  682. this.update_variants();
  683. },
  684. add_font: function() {
  685. var variants;
  686. var font = google_fonts_storage.get_fonts().findWhere({ 'family': this.font_family_select.get_value() });
  687. if (_.isEmpty(font)) {
  688. alert(l10n.choose_font_weight);
  689. return;
  690. }
  691. variants = this.choose_variants.get_selected();
  692. if (_.isEmpty(variants)) {
  693. alert(l10n.choose_one_font_weight);
  694. return;
  695. }
  696. _.each(variants, function(variant) {
  697. theme_fonts_collection.add({
  698. id: font.get('family') + variant,
  699. font: font.toJSON(),
  700. variant: variant
  701. });
  702. });
  703. this.update_variants();
  704. },
  705. load_google_fonts: function(fonts_collection) {
  706. var add_font_panel = this.$el.find('.add-font-panel');
  707. var typefaces_list = [{ label: l10n.click_to_pick_google_font, value: ''}];
  708. _.each(fonts_collection.pluck('family'), function(family) {
  709. typefaces_list.push({ label: family, value: family });
  710. });
  711. add_font_panel.find('.loading-fonts').remove();
  712. // Select font
  713. this.font_family_select = new Fields.Chosen_Select({
  714. label: l10n.typeface,
  715. values: typefaces_list,
  716. placeholder: l10n.choose_font,
  717. additional_classes: 'choose-font'
  718. });
  719. this.font_family_select.render();
  720. add_font_panel.find('.font-weights-list').before(this.font_family_select.el);
  721. $('.upfront-chosen-select', this.$el).chosen({
  722. width: '289px'
  723. });
  724. this.listenTo(this.font_family_select, 'changed', this.update_variants);
  725. },
  726. update_variants_on_remove: function() {
  727. this.update_variants();
  728. },
  729. update_variants: function(model) {
  730. this.$el.find('.font-weights-list').css('background', 'white');
  731. if (!model) model = google_fonts_storage.get_fonts().findWhere({ 'family' : this.font_family_select.get_value() });
  732. if (!model) return;
  733. // Choose variants
  734. var variants = new Backbone.Collection();
  735. _.each(model.get('variants'), function(variant) {
  736. // Add font to page so we can make preview with real fonts
  737. if ($('#' + model.get('family').toLowerCase() + variant + '-css').length === 0) {
  738. $('head').append('<link rel="stylesheet" id="' + model.get('family').toLowerCase() + '-' + variant + '-css" href="//fonts.googleapis.com/css?family=' + model.get('family') + '%3A' + variant + '" type="text/css" media="all">');
  739. }
  740. var weight_style = Font_Model.parse_variant(variant);
  741. variants.add({
  742. family: model.get('family'),
  743. name: Font_Model.normalize_variant(variant),
  744. variant: variant,
  745. already_added: !!theme_fonts_collection.get(model.get('family') + variant),
  746. weight: weight_style.weight,
  747. style: weight_style.style
  748. });
  749. });
  750. if (this.choose_variants) this.choose_variants.remove();
  751. this.choose_variants = new Font_Variants_Preview({
  752. collection: variants,
  753. heading_preview: this.heading_preview
  754. });
  755. this.choose_variants.render();
  756. this.$el.find('.font-weights-list-wrapper').html(this.choose_variants.el);
  757. },
  758. set_ok_button: function(button) {
  759. button.on('click', this.on_ok_click);
  760. },
  761. on_ok_click: function(event) {
  762. Upfront.Events.trigger("upfront:render_typography_sidebar");
  763. if (Upfront.mainData.userDoneFontsIntro) return;
  764. Upfront.Util.post({action: "upfront_user_done_font_intro"});
  765. Upfront.mainData.userDoneFontsIntro = true;
  766. }
  767. });
  768. var Insert_Font_Widget = Backbone.View.extend({
  769. initialize: function() {
  770. var me = this;
  771. this.fields = [
  772. new Fields.Typeface_Chosen_Select({
  773. label: '',
  774. compact: true,
  775. values: theme_fonts_collection.get_fonts_for_select(),
  776. additional_classes: 'choose-typeface',
  777. select_width: '230px'
  778. }),
  779. new Fields.Typeface_Style_Chosen_Select({
  780. label: '',
  781. compact: true,
  782. values: [],
  783. additional_classes: 'choose-variant',
  784. select_width: '120px'
  785. }),
  786. new Fields.Button({
  787. label: l10n.insert_font,
  788. compact: true,
  789. on_click: function(){
  790. me.preview_font();
  791. }
  792. })
  793. ];
  794. this.is_responsive = ( Upfront.Application.get_current() === Upfront.Settings.Application.MODE.RESPONSIVE );
  795. },
  796. render: function() {
  797. $('#insert-font-widget').html('').addClass('open');
  798. this.$el.html('');
  799. _.each(this.fields, function(field) {
  800. field.render();
  801. this.$el.append(field.el);
  802. }, this);
  803. this.listenTo(this.fields[0], 'changed', function() {
  804. var variants = theme_fonts_collection.get_variants(this.fields[0].get_value());
  805. this.render_variants(variants);
  806. });
  807. $('.choose-typeface select', this.$el).chosen({
  808. width: '230px',
  809. disable_search: true
  810. });
  811. $('.choose-variant select', this.$el).chosen({
  812. width: '120px',
  813. disable_search: true
  814. });
  815. return this;
  816. },
  817. render_variants: function(variants) {
  818. var $variant_field = this.$el.find('.choose-variant select');
  819. $variant_field.find('option').remove();
  820. $variant_field.append('<option value="">' + l10n.choose_variant + '</option>');
  821. _.each(variants, function(variant) {
  822. $variant_field.append('<option value="' + variant + '">' + variant + '</option>');
  823. });
  824. $variant_field.trigger('chosen:updated');
  825. },
  826. preview_font: function() {
  827. var font_value = this.fields[0].get_value();
  828. if ( !font_value ) {
  829. this.finish();
  830. return;
  831. }
  832. this.replaceFont({
  833. font_family: font_value,
  834. variant: Font_Model.parse_variant(this.fields[1].get_value())
  835. });
  836. },
  837. replaceFont: function(font) {
  838. var lines;
  839. this.editor = ( this.is_responsive ) ? Upfront.Application.generalCssEditor.editor : Upfront.Application.cssEditor.editor;
  840. this.style_doc = this.editor.getSession().getDocument();
  841. this.last_selected_font = font;
  842. // Insert selected font family
  843. if (!this.font_family_range) {
  844. this.font_family_range = this.editor.getSelection().getRange();
  845. } else {
  846. this.font_family_range.end = this.end_point;
  847. }
  848. this.end_point = this.style_doc.replace(this.font_family_range, font.font_family);
  849. // Insert selected weight and style, first reset them
  850. this.reset_properties();
  851. lines = [];
  852. if (font.variant.weight) {
  853. lines.push(' font-weight: ' + font.variant.weight + ';');
  854. }
  855. if (font.variant.style) {
  856. lines.push(' font-style: ' + font.variant.style + ';');
  857. }
  858. if (lines.length > 0) {
  859. this.style_doc.insertLines(this.font_family_range.start.row + 1, lines);
  860. }
  861. this.finish();
  862. },
  863. reset_properties: function() {
  864. var row, line, result;
  865. this.editor = ( this.is_responsive ) ? Upfront.Application.generalCssEditor.editor : Upfront.Application.cssEditor.editor;
  866. this.style_doc = this.editor.getSession().getDocument();
  867. // Search forward only from font family row since lower properties override upper
  868. result = {};
  869. row = this.font_family_range.start.row + 1;
  870. line = this.style_doc.getLine(row);
  871. while (line.indexOf('}') < 0) {
  872. if (line.indexOf('font-weight') !== -1) {
  873. result.weight = row;
  874. if (!this.starting_weight) this.starting_weight = line;
  875. }
  876. if (line.indexOf('font-style') !== -1) {
  877. result.style = row;
  878. if (!this.starting_style) this.starting_style = line;
  879. }
  880. row++;
  881. line = this.style_doc.getLine(row);
  882. if (!line) {
  883. // Fix missing closing paren
  884. //this.style_doc.insertLines(row, ['}']); // This adds a standalone new brace for some reason
  885. break;
  886. }
  887. }
  888. // Reset properties. This is complicated. If both font style and font weight properties are in current style rule
  889. // we need to remove them carefully because when we remove first, seconds' row number might change
  890. // so first remove one with higher row number.
  891. if (result.weight && result.style) {
  892. if (result.weight > result.style) {
  893. this.style_doc.removeLines(result.weight, result.weight);
  894. this.style_doc.removeLines(result.style, result.style);
  895. } else {
  896. this.style_doc.removeLines(result.style, result.style);
  897. this.style_doc.removeLines(result.weight, result.weight);
  898. }
  899. result.weight = false;
  900. result.style = false;
  901. }
  902. if (result.weight) {
  903. this.style_doc.removeLines(result.weight, result.weight);
  904. }
  905. if (result.style) {
  906. this.style_doc.removeLines(result.style, result.style);
  907. }
  908. },
  909. finish: function() {
  910. $('#insert-font-widget').html('<a class="upfront-css-font" href="#">' + l10n.insert_font + '</a>').removeClass('open');
  911. }
  912. });
  913. return {
  914. "System": system_fonts_storage,
  915. "Google": google_fonts_storage,
  916. Text_Fonts_Manager: Text_Fonts_Manager,
  917. Icon_Fonts_Manager: Icon_Fonts_Manager,
  918. theme_fonts_collection: theme_fonts_collection,
  919. icon_fonts_collection: icon_fonts_collection,
  920. Insert_Font_Widget: Insert_Font_Widget,
  921. Model: Font_Model
  922. };
  923. });
  924. })(jQuery);