Created
November 19, 2021 20:57
-
-
Save brendon/3d980213d69a839013bd337a1cca3f10 to your computer and use it in GitHub Desktop.
Google Fonts for Froala 3.x and probably 4.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* 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' | |
}); | |
}))); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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"; | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
resources :webfonts, only: [] do | |
collection do | |
get :simple | |
get :detailed | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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