Skip to content

Commit 8546f4f

Browse files
texpertjunie-agent
andcommitted
Fix: fix missing TinyMCE icons in development by patching AssetUrlProcessor
Co-authored-by: Junie <junie@jetbrains.com>
1 parent 67b3dbb commit 8546f4f

3 files changed

Lines changed: 98 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- **Fix:** Restore TinyMCE editor icons in development, [#1183](https://github.com/owen2345/camaleon-cms/pull/1183)
6+
- sprockets-rails >= 3.5 registers `Sprockets::Rails::AssetUrlProcessor`, a `text/css` post-processor that rewrites every relative `url(...)` reference to a digested asset path. TinyMCE's bundled skin (`tinymce/skins/lightgray/skin.min.css`) references its icon font with relative urls such as `url("fonts/tinymce.woff")`, whose real logical path is `tinymce/skins/lightgray/fonts/...`. The processor cannot resolve them and rewrites them to an invalid root path (`/fonts/tinymce.woff`) that 404s, leaving the editor toolbar without icons.
7+
- Only surfaces in environments that compile assets on the fly (development): production ships the skin as raw static files via `tinymce.install = :copy`, bypassing Sprockets (and the processor) entirely. The processor must not be disabled globally, however, because other stylesheets (e.g. Bootstrap, whose glyphicon `@font-face` urls are relative) rely on it to produce resolvable asset paths. `config/initializers/assets.rb` now replaces it (when `config.assets.compile` is true) with a `TinymceSkinSafeAssetUrlProcessor` subclass that, for the TinyMCE skin only, first expands the skin's directory-relative font urls into full logical asset paths (prefixed with the skin's own logical directory) before delegating to the original processor, which then resolves and digests them correctly; every other stylesheet is processed unchanged. Bumped `config.assets.version` so host apps invalidate any stale cached skin asset. Added a regression spec covering the swap, the url expansion/digesting and that the icon font resolves.
8+
59
- **Fix:** Restore `object_class` scoping on `CustomField#metas` and `CustomFieldGroup#metas`, [#1183](https://github.com/owen2345/camaleon-cms/pull/1183)
610
- Regression introduced in #1173 (polymorphic meta ownership) dropped the `-> { where(object_class: 'CustomField' / 'CustomFieldGroup') }` scope from these associations. Meta rows are keyed by both `objectid` and `object_class`, so when a custom field's numeric id collided with another model's id (e.g. a `Post` sharing the same id), `get_option('field_key')` read the wrong `_default` meta and returned `nil`.
711
- Visible symptom: on **General Site → Theme settings**, the `editor` Footer message field rendered as a plain `text_box` (no TinyMCE editor frame), because `_render.html.erb` fell back to `text_box` when `field_key` resolved to nil.

config/initializers/assets.rb

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
11
# Be sure to restart your server when you modify this file.
22

33
# Version of your assets, change this if you want to expire all your assets.
4-
Rails.application.config.assets.version = '1.0'
4+
Rails.application.config.assets.version = '1.1'
55

66
Rails.application.config.tinymce.install = :copy
77

8+
# TinyMCE editor icons in development.
9+
#
10+
# sprockets-rails >= 3.5 registers `Sprockets::Rails::AssetUrlProcessor`, a `text/css`
11+
# post-processor that rewrites every relative `url(...)` reference to a digested asset
12+
# path. TinyMCE's bundled skin (`tinymce/skins/lightgray/skin.min.css`) references its
13+
# icon font with directory-relative urls such as `url('fonts/tinymce.woff')`. Sprockets
14+
# resolves `url()` paths against the asset load roots (not the referencing file's own
15+
# directory), so it cannot find `fonts/tinymce.woff` (its real logical path is
16+
# `tinymce/skins/lightgray/fonts/...`) and rewrites it to an invalid root path
17+
# (`/fonts/tinymce.woff`) which 404s, leaving the editor toolbar without icons.
18+
#
19+
# In production this never happens: `tinymce.install = :copy` ships the skin as raw static
20+
# files under `public/assets`, bypassing Sprockets (and this processor) entirely. The bug
21+
# only surfaces in environments that compile assets on the fly (development).
22+
#
23+
# We must NOT disable the processor globally: other stylesheets (e.g. Bootstrap, whose
24+
# glyphicon @font-face urls are relative) rely on it to produce resolvable digested paths.
25+
# Instead, swap it for a thin subclass that, for the TinyMCE skin only, first expands the
26+
# skin's directory-relative font urls into full logical asset paths (prefixing them with the
27+
# skin's own logical directory) before delegating to the original processor. The original
28+
# then resolves and digests them correctly. Every other stylesheet is processed unchanged.
29+
if defined?(Sprockets::Rails::AssetUrlProcessor) && Rails.application.config.assets.compile &&
30+
Sprockets.respond_to?(:unregister_postprocessor) && Sprockets.respond_to?(:register_postprocessor)
31+
class TinymceSkinSafeAssetUrlProcessor < Sprockets::Rails::AssetUrlProcessor
32+
SKIN_MARKER = 'tinymce/skins/'.freeze
33+
# Directory-relative `url(...)` references that are not absolute/external/data urls.
34+
RELATIVE_URL = %r{url\(\s*["']?(?!(?:#|data|http|/))(?:\./)?(?<path>[^"'\s)]+)\s*["']?\)}
35+
36+
def self.call(input)
37+
filename = input[:filename].to_s
38+
return super unless filename.include?(SKIN_MARKER)
39+
40+
logical_dir = File.dirname(filename[filename.index('tinymce/')..])
41+
rewritten = input[:data].gsub(RELATIVE_URL) do
42+
"url(#{logical_dir}/#{Regexp.last_match(:path)})"
43+
end
44+
super(input.merge(data: rewritten))
45+
end
46+
end
47+
48+
Sprockets.unregister_postprocessor('text/css', Sprockets::Rails::AssetUrlProcessor)
49+
Sprockets.register_postprocessor('text/css', TinymceSkinSafeAssetUrlProcessor)
50+
end
51+
852
# Add additional assets to the asset load path
953
Rails.application.config.assets.precompile += %w[camaleon_cms/*]
1054
# Rails.application.config.assets.precompile += %w( themes/*/assets/* )
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
# Regression: sprockets-rails >= 3.5 registers `Sprockets::Rails::AssetUrlProcessor`, which rewrites
6+
# relative `url(...)` references in CSS to digested asset paths. TinyMCE's bundled skin references its
7+
# icon font with directory-relative urls (e.g. `url('fonts/tinymce.woff')`). Sprockets resolves
8+
# `url()` paths against the asset load roots (not the referencing file's own directory), so the
9+
# default processor cannot find them and rewrites them to an invalid root path (`/fonts/...`) that
10+
# 404s, breaking the editor toolbar icons in development.
11+
#
12+
# We cannot disable the processor globally because other stylesheets (e.g. Bootstrap glyphicons) rely
13+
# on it. `config/initializers/assets.rb` swaps it for a subclass that, for the TinyMCE skin only,
14+
# expands the directory-relative font urls into full logical asset paths before delegating to the
15+
# original processor, so they resolve to proper digested paths while every other stylesheet is
16+
# processed unchanged.
17+
RSpec.describe 'TinyMCE skin asset urls', type: :request do
18+
let(:skin_css) { Rails.application.assets&.find_asset('tinymce/skins/lightgray/skin.min.css') }
19+
20+
before { skip('Sprockets is not serving assets in this environment') if skin_css.nil? }
21+
22+
it 'replaces the default AssetUrlProcessor with the tinymce-safe subclass' do
23+
expect(Rails.application.config.assets.compile).to be(true)
24+
registered = (Sprockets.postprocessors['text/css'] || []).map(&:to_s)
25+
expect(registered).to include(a_string_matching(/TinymceSkinSafeAssetUrlProcessor/))
26+
expect(registered).not_to include('Sprockets::Rails::AssetUrlProcessor')
27+
end
28+
29+
it 'rewrites the skin font urls to resolvable digested asset paths (icons load in development)' do
30+
# The relative reference must be gone, replaced by a digested path under the skin directory.
31+
expect(skin_css.source).not_to match(%r{url\(["']?(?:\./)?fonts/tinymce\.woff})
32+
expect(skin_css.source).not_to match(%r{url\(["']?/fonts/tinymce\.woff})
33+
expect(skin_css.source)
34+
.to match(%r{url\(/assets/tinymce/skins/lightgray/fonts/tinymce-[0-9a-f]+\.woff\)})
35+
end
36+
37+
it 'exposes the icon font as a resolvable asset' do
38+
expect(Rails.application.assets.find_asset('tinymce/skins/lightgray/fonts/tinymce.woff')).not_to be_nil
39+
end
40+
41+
it 'expands directory-relative skin font urls to full logical paths before delegating' do
42+
env = Rails.application.assets
43+
css = 'a{src:url("fonts/tinymce.woff")}'
44+
input = { environment: env, filename: skin_css.filename, data: css,
45+
name: skin_css.logical_path, content_type: 'text/css', metadata: {} }
46+
out = TinymceSkinSafeAssetUrlProcessor.call(input)
47+
expect(out[:data]).to match(%r{url\(/assets/tinymce/skins/lightgray/fonts/tinymce-[0-9a-f]+\.woff\)})
48+
end
49+
end

0 commit comments

Comments
 (0)