Skip to content

Instantly share code, notes, and snippets.

@brendon
Created November 19, 2021 20:57
Show Gist options
  • Save brendon/3d980213d69a839013bd337a1cca3f10 to your computer and use it in GitHub Desktop.
Save brendon/3d980213d69a839013bd337a1cca3f10 to your computer and use it in GitHub Desktop.
Google Fonts for Froala 3.x and probably 4.x
// You need to add these two configuration options when initialising froala:
googleFonts: true,
googleFontsAddress: `https://${ASSETS_ADDRESS}/webfonts/detailed`
// The address can be anything but the expected structure is fixed and you should be able to infer that from
// the font_family.js. I've included the controller I use to generate the cached copy of the webfonts list.
// Don't be tempted to hit google all the time with your key as you'll soon reach your fair use limit.
// When rendering the page, add data-controller="froala--webfonts" to the surrounding tag (if using my stimulus controller)
/*!
* froala_editor v3.2.6-1 (https://www.froala.com/wysiwyg-editor)
* License https://froala.com/wysiwyg-editor/terms/
* Copyright 2014-2021 Froala Labs
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('froala-editor')) :
typeof define === 'function' && define.amd ? define(['froala-editor'], factory) :
(factory(global.FroalaEditor));
}(this, (function (FE) { 'use strict';
var WebFont = require('webfontloader')
FE = FE && FE.hasOwnProperty('default') ? FE['default'] : FE;
Object.assign(FE.DEFAULTS, {
fontFamily: {
'Arial,Helvetica,sans-serif': 'Arial',
'Georgia,serif': 'Georgia',
'Impact,Charcoal,sans-serif': 'Impact',
'Tahoma,Geneva,sans-serif': 'Tahoma',
'Times New Roman,Times,serif,-webkit-standard': 'Times New Roman',
'Verdana,Geneva,sans-serif': 'Verdana'
},
fontFamilySelection: false,
fontFamilyDefaultSelection: 'Font Family',
googleFonts: false,
googleFontsAddress: '',
fontFamilyThemeElements: ['h1', 'h2', 'h3', 'h4']
});
FE.PLUGINS.fontFamily = function (editor) {
var $ = editor.$;
var loadedGoogleFonts = [];
var waitForInitialized = new Promise(function(resolve) { editor.events.on('initialized', resolve) });
var waitForHtmlSet = new Promise(function(resolve) { editor.events.on('html.set', resolve) });
if (editor.opts.googleFonts) {
var _getGoogleFonts = jQuery.getJSON(editor.opts.googleFontsAddress)
.then(function(data) { return data.items })
.catch(function() { return [] });
}
function _init() {
var $dropdown = $('#dropdown-menu-fontFamily'.concat("-", editor.id));
var keyBuffer = [];
var lastKeyTime = Date.now();
// Prevent the dropdown from closing due to an alpha keypress inside
$dropdown.on('keydown', function(event) {
if (editor.keys.isCharacter(event.which) && !editor.keys.ctrlKey(event)) {
var key = event.key.toLowerCase();
var currentTime = Date.now();
if (currentTime - lastKeyTime > 1000) {
keyBuffer = [];
}
keyBuffer.push(key);
lastKeyTime = currentTime;
var $match = $dropdown.find('.fr-command[data-search^="' + keyBuffer.join('') + '"]');
if ($match.length > 0) editor.accessibility.focusToolbarElement($match);
// Prevent the dropdown from closing due to an alpha keypress inside
event.preventDefault();
event.stopPropagation();
}
});
waitForInitialized
.then(_applyGoogleFonts)
.then(function() { return waitForHtmlSet })
.then(_setThemeFonts);
editor.events.on('paste.after codeView.update', _loadUsedGoogleFonts);
}
function apply(val) {
if (editor.opts.googleFonts) _loadGoogleFont(val);
_setRecentFontFamily(val);
editor.format.applyStyle('font-family', val);
}
function refreshOnShow($btn, $dropdown) {
$dropdown.find('.fr-command.fr-active').removeClass('fr-active').attr('aria-selected', false);
$dropdown.find(".fr-command[data-param1=\"".concat(_getSelection(), "\"]")).addClass('fr-active').attr('aria-selected', true);
}
function _getArray(val) {
var font_array = val.replace(/(sans-serif|serif|monospace|cursive|fantasy)/gi, '').replace(/"|'| /g, '').split(',');
return $(this).grep(font_array, function (txt) {
return txt.length > 0;
});
}
/**
* Return first match position.
*/
function _matches(array1, array2) {
for (var i = 0; i < array1.length; i++) {
for (var j = 0; j < array2.length; j++) {
if (array1[i].toLowerCase() === array2[j].toLowerCase()) {
return [i, j];
}
}
}
return null;
}
function _getSelection() {
var val = $(editor.selection.element()).css('font-family');
var font_array = _getArray(val);
var font_matches = [];
for (var key in editor.opts.fontFamily) {
if (editor.opts.fontFamily.hasOwnProperty(key)) {
var c_font_array = _getArray(key);
var match = _matches(font_array, c_font_array);
if (match) {
font_matches.push([key, match]);
}
}
}
if (font_matches.length === 0) return null; // Sort matches by their position.
// Times,Arial should be detected as being Times, not Arial.
font_matches.sort(function (a, b) {
var f_diff = a[1][0] - b[1][0];
if (f_diff === 0) {
return a[1][1] - b[1][1];
} else {
return f_diff;
}
});
return font_matches[0][0];
}
function refresh($btn) {
if (editor.opts.fontFamilySelection) {
var val = $(editor.selection.element()).css('font-family').replace(/(sans-serif|serif|monospace|cursive|fantasy)/gi, '').replace(/"|'|/g, '').split(',');
$btn.find('> span').text(editor.opts.fontFamily[_getSelection()] || val[0] || editor.language.translate(editor.opts.fontFamilyDefaultSelection));
}
}
function _setRecentFontFamily(fontFamilyCSS) {
var fontFamily = _firstFontFamily(fontFamilyCSS);
var $dropdown = $('#dropdown-menu-fontFamily'.concat("-", editor.id));
var $recentDropdownList = $dropdown.find('.fr-dropdown-list.recent');
var $usedFontFamily = $dropdown.find('.fr-dropdown-list.origin .fr-command[title="' + fontFamily + '"]').parent();
if ($usedFontFamily.length > 0) {
$recentDropdownList.addClass('populated');
$recentDropdownList.append($usedFontFamily);
}
}
function _applyGoogleFonts() {
if (!editor.opts.googleFonts) return Promise.resolve();
console.debug('Applying Google Fonts');
return _getGoogleFonts.then(function(googleFonts) {
var $dropdown = $('#dropdown-menu-fontFamily'.concat("-", editor.id));
var $dropdownList = $dropdown.find('.fr-dropdown-list.google');
var intersectionObserverOptions = {
root: $dropdown[0],
rootMargin: '0px 0px 400px 0px'
};
function onIntersection(entries, observer) {
jQuery.each(entries, function(index, entry) {
if (entry.isIntersecting) {
_loadGoogleFontPreview(entry.target.firstChild);
observer.unobserve(entry.target);
}
});
}
var observer = new IntersectionObserver(onIntersection, intersectionObserverOptions);
const fragment = document.createDocumentFragment();
googleFonts.forEach(function(font) {
if (font.variants.includes('regular')) {
const fontFamilyCSS = `'${font.family}',${font.category}`;
editor.opts.fontFamily[fontFamilyCSS] = font.family;
const listItem = document.createElement('li');
listItem.setAttribute('role', 'presentation');
fragment.appendChild(listItem);
const anchor = document.createElement('a');
anchor.setAttribute('class', 'fr-command');
anchor.setAttribute('tabIndex', '-1');
anchor.setAttribute('role', 'option');
anchor.setAttribute('data-cmd', 'fontFamily');
anchor.setAttribute('data-param1', fontFamilyCSS);
anchor.setAttribute('title', font.family);
anchor.setAttribute('data-search', font.family.toLowerCase());
listItem.appendChild(anchor);
const content = document.createTextNode(font.family);
anchor.appendChild(content);
observer.observe(listItem);
}
})
$dropdownList[0].appendChild(fragment);
$dropdownList.addClass('populated');
_loadUsedGoogleFonts();
});
}
function _loadGoogleFontPreview(anchor) {
const fontFamily = anchor.title
if (_googleFontIsNotLoaded(fontFamily)) {
console.debug('Loading Google Font Preview: ' + fontFamily)
anchor.setAttribute('style', `font-family: ${anchor.dataset.param1}`);
WebFont.load({ google: { families: [fontFamily], text: fontFamily } });
}
}
function _loadUsedGoogleFonts() {
var $fontNodes = editor.$el.find('.fr-view [style*="font-family"]');
$fontNodes.each(function() {
var fontFamilyCSS = $(this).css('font-family');
_setRecentFontFamily(fontFamilyCSS);
_loadGoogleFont(fontFamilyCSS);
});
}
function _setThemeFonts() {
console.debug('Setting Theme Fonts');
var contentWindow = editor.opts.iframe ? editor.$iframe[0].contentWindow : window;
var themeFonts = [];
var $dropdown = $('#dropdown-menu-fontFamily'.concat("-", editor.id));
var $themeDropdownList = $dropdown.find('.fr-dropdown-list.theme');
// Body font
themeFonts.push(contentWindow.getComputedStyle(editor.el).getPropertyValue('font-family'));
jQuery.each(editor.opts.fontFamilyThemeElements, function(index, element) {
var $element = $('<' + element + ' style="display: none;">');
editor.$el.append($element);
themeFonts.push(contentWindow.getComputedStyle($element[0]).getPropertyValue('font-family'));
$element.remove();
});
jQuery.each(themeFonts, function(index, fontFamilyCSS) {
var fontFamily = _firstFontFamily(fontFamilyCSS);
var $usedFontFamily = $dropdown.find('.fr-dropdown-list.origin .fr-command[title="' + fontFamily + '"]').parent();
if ($usedFontFamily.length > 0) {
$themeDropdownList.addClass('populated');
$themeDropdownList.append($usedFontFamily);
}
});
}
function _loadGoogleFont(fontFamilyCSS) {
var fontFamily = _firstFontFamily(fontFamilyCSS);
_isGoogleFont(fontFamily).then(function(is) {
if (is) {
if (_googleFontIsNotLoaded(fontFamily)) {
loadedGoogleFonts.push(fontFamily);
console.debug('Loading Google Font: ' + fontFamily);
WebFont.load({
google: {
families: [fontFamily + ':regular,italic,700,700italic:latin-ext']
},
context: editor.opts.iframe ? editor.$iframe.get(0).contentWindow : window
});
}
}
});
}
function _isGoogleFont(fontFamily) {
return _getGoogleFonts.then(function(googleFonts) {
var is = false;
jQuery.each(googleFonts, function(index, font) {
if (font.family === fontFamily) {
is = true;
return false;
}
});
return is;
});
}
function _googleFontIsNotLoaded(fontFamily) {
return jQuery.inArray(fontFamily, loadedGoogleFonts) === -1
}
function _firstFontFamily(fontFamilyCSS) {
return fontFamilyCSS.split(',')[0].replace(/['"]+/g, '').trim();
}
return {
_init: _init,
apply: apply,
refreshOnShow: refreshOnShow,
refresh: refresh
};
}; // Register the font size command.
FE.RegisterCommand('fontFamily', {
type: 'dropdown',
displaySelection: function displaySelection(editor) {
return editor.opts.fontFamilySelection;
},
defaultSelection: function defaultSelection(editor) {
return editor.opts.fontFamilyDefaultSelection;
},
html: function html() {
var c = '<ul class="fr-dropdown-list theme" role="presentation"></ul>';
c += '<ul class="fr-dropdown-list recent" role="presentation"></ul>';
c += '<ul class="fr-dropdown-list system origin" role="presentation">';
var options = this.opts.fontFamily;
for (var val in options) {
if (options.hasOwnProperty(val)) {
c += "<li role=\"presentation\">".concat("<a class=\"fr-command\" tabIndex=\"-1\" role=\"option\" data-cmd=\"fontFamily\" data-param1=\"").concat(val, "\" style=\"font-family: ").concat(val, "\" title=\"").concat(options[val], "\" data-search=\"").concat(options[val].toLowerCase(), "\">").concat(options[val], "</a></li>");
}
}
c += '</ul>';
c += '<ul class="fr-dropdown-list google origin" role="presentation"></ul>';
return c;
},
title: 'Font Family',
callback: function callback(cmd, val) {
this.fontFamily.apply(val);
},
refresh: function refresh($btn) {
this.fontFamily.refresh($btn);
},
refreshOnShow: function refreshOnShow($btn, $dropdown) {
this.fontFamily.refreshOnShow($btn, $dropdown);
},
plugin: 'fontFamily'
}); // Add the font size icon.
FE.DefineIcon('fontFamily', {
NAME: 'font',
SVG_KEY: 'fontFamily'
});
})));
.fr-command.fr-btn + .fr-dropdown-menu[id^="dropdown-menu-fontFamily"] {
.fr-dropdown-wrapper .fr-dropdown-content {
ul.fr-dropdown-list {
&:before {
font-size: 10px;
color: #999999;
text-transform: uppercase;
display: block;
padding: 0 10px 8px;
}
&.system:before {
content: "System Fonts";
}
&:not(.system) {
display: none;
&.populated {
display: block;
}
&.theme {
&:before {
content: "Theme Fonts";
}
}
&.recent {
&:before {
content: "Recent Fonts";
}
}
&.google {
&:before {
content: "Google Fonts";
}
}
}
}
}
}
--- app/javascript/libraries/froala-editor/js/froala_editor.js 2021-06-12 21:43:48.000000000 +1200
+++ app/javascript/libraries/froala-editor/js/fixed_froala_editor.js 2021-06-12 21:43:45.000000000 +1200
@@ -8424,8 +8422,8 @@
var disabled = editor.edit.isDisabled();
editor.edit.on();
- editor.core.injectStyle(editor.opts.iframeDefaultStyle + editor.opts.iframeStyle);
-
+ editor.core.injectStyle(editor.opts.iframeDefaultStyle + editor.opts.iframeStyle)
+ .then(function() {
_normalize();
if (!editor.opts.useClasses) {
@@ -8447,6 +8445,7 @@
editor.events.trigger('html.set'); //https://github.com/froala-labs/froala-editor-js-2/issues/1920
editor.events.trigger('charCounter.update');
+ });
}
function _specifity(selector) {
@@ -11471,13 +11470,30 @@
editor.$head.find('style[data-fr-style], link[data-fr-style]').remove();
editor.$head.append("<style data-fr-style=\"true\">".concat(style, "</style>"));
+ var promises = [];
+
for (var i = 0; i < editor.opts.iframeStyleFiles.length; i++) {
var $link = $("<link data-fr-style=\"true\" rel=\"stylesheet\" href=\"".concat(editor.opts.iframeStyleFiles[i], "\">")); // Listen to the load event in order to sync iframe.
- $link.get(0).addEventListener('load', editor.size.syncIframe); // Append to the head.
+ promises.push(new Promise(function(resolve) {
+ $link.get(0).addEventListener('load', function() {
+ resolve();
+ });
+ $link.get(0).addEventListener('error', function(event) {
+ console.warn('Errror loading iframeStyleFile ' + event.target.href);
+ resolve();
+ });
+ }));
+ // Append to the head.
editor.$head.append($link);
}
+
+ return Promise.all(promises).then(function() {
+ editor.size.syncIframe();
+ });
+ } else {
+ return Promise.resolve();
}
}
@@ -13014,12 +13030,22 @@
if (!$destination.length) {
+ var $list = editor.shared.$f_el.closest('ul')
+
if (down) {
+ if ($list.next('ul').length) {
+ $destination = $list.next('ul').find('.fr-command:not(.fr-disabled)').first();
+ } else {
$destination = editor.shared.$f_el.closest('.fr-dropdown-menu').find('.fr-command:not(.fr-disabled)').first();
+ }
+ } else {
+ if ($list.prev('ul').length) {
+ $destination = $list.prev('ul').find('.fr-command:not(.fr-disabled)').last();
} else {
$destination = editor.shared.$f_el.closest('.fr-dropdown-menu').find('.fr-command:not(.fr-disabled)').last();
}
}
+ }
focusToolbarElement($destination);
return false;
resources :webfonts, only: [] do
collection do
get :simple
get :detailed
end
end
// This is a Stimulus Controller for fetching the required fonts when showing the page on the front-end
import WebFont from 'webfontloader'
import some from 'lodash/some'
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
async connect() {
const controller = this
const webFonts = await this.fetchWebFonts()
$(this.element).find('[style*="font-family"]').each(function() {
const fontFamily = controller.getFirstFontFamily($(this).css('font-family'))
if(some(webFonts, { family: fontFamily })) {
WebFont.load({ google: { families: [`${fontFamily}:regular,italic,700,700italic:latin-ext`] } })
}
})
}
fetchWebFonts() {
return $.getJSON(`https://${ASSETS_ADDRESS}/webfonts/simple`).then(data => data.items)
}
getFirstFontFamily(declaration) {
return declaration.split(',')[0].replace(/['"]+/g, '').trim()
}
}
class WebfontsController < ActionController::Base
API_URL = "https://www.googleapis.com/webfonts/v1/webfonts"
API_KEY = 'ADD_YOUR_WEBFONTS_KEY_HERE'
def simple
expires_in 1.day, public: true
render json: fetch(sort: 'popularity', fields: 'items(family)')
end
def detailed
expires_in 1.day, public: true
render json: fetch(sort: 'alpha', fields: 'items(family,category,variants)')
end
private
def fetch(query)
Faraday.get(API_URL, { key: API_KEY, **query }).body
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment