mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-29 00:44:05 +09:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1376a97ee | ||
|
|
d3f22d78e1 |
No files matched your search
+4
-4
@@ -375,10 +375,10 @@ GEM
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.1.8)
|
||||
thor (1.4.0)
|
||||
thruster (0.1.15-aarch64-linux)
|
||||
thruster (0.1.15-arm64-darwin)
|
||||
thruster (0.1.15-x86_64-darwin)
|
||||
thruster (0.1.15-x86_64-linux)
|
||||
thruster (0.1.23-aarch64-linux)
|
||||
thruster (0.1.23-arm64-darwin)
|
||||
thruster (0.1.23-x86_64-darwin)
|
||||
thruster (0.1.23-x86_64-linux)
|
||||
tilt (2.6.1)
|
||||
timeout (0.6.1)
|
||||
tsort (0.2.0)
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
class Accounts::CustomStylesController < ApplicationController
|
||||
allow_unauthenticated_access only: :show
|
||||
before_action :ensure_can_administer, :set_account, except: :show
|
||||
|
||||
def show
|
||||
if stale?(etag: Current.account)
|
||||
expires_in 5.minutes, public: true, stale_while_revalidate: 1.week
|
||||
|
||||
render plain: Current.account&.custom_styles.to_s, content_type: "text/css"
|
||||
end
|
||||
end
|
||||
before_action :ensure_can_administer, :set_account
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
@@ -12,6 +12,12 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def custom_styles_tag
|
||||
if custom_styles = Current.account&.custom_styles
|
||||
tag.style(custom_styles.to_s.html_safe, data: { turbo_track: "reload" })
|
||||
end
|
||||
end
|
||||
|
||||
def body_classes
|
||||
[ @body_class, admin_body_class, account_logo_body_class ].compact.join(" ")
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module ContentFilters
|
||||
TextMessagePresentationFilters = ActionText::Content::Filters.new(RemoveSoloUnfurledLinkText, StyleUnfurledTwitterAvatars, SanitizeTags)
|
||||
TextMessagePresentationFilters = ActionText::Content::Filters.new(RemoveSoloUnfurledLinkText, StyleUnfurledTwitterAvatars, SanitizeTags, SanitizeAttributes)
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
class ContentFilters::SanitizeAttributes < ActionText::Content::Filter
|
||||
def applicable?
|
||||
true
|
||||
end
|
||||
|
||||
# Scrub attributes on the tags that SanitizeTags allows, using Rails' safe-list
|
||||
# sanitizer so unsafe URI schemes and event-handler attributes are stripped.
|
||||
# Runs after SanitizeTags, so passing the same allowed tags makes the tag pass
|
||||
# a no-op and only attributes are scrubbed.
|
||||
def apply
|
||||
sanitizer.sanitize fragment.to_html, tags: allowed_tags, attributes: allowed_attributes
|
||||
end
|
||||
|
||||
private
|
||||
# Presentation styling relies on class attributes (e.g. unfurled link embeds),
|
||||
# which the standard ActionText set doesn't include.
|
||||
EXTRA_ALLOWED_ATTRIBUTES = %w[ class ]
|
||||
|
||||
# ActionText::ContentHelper.sanitizer is a single process-wide instance, and
|
||||
# rails-html-sanitizer reuses one mutable permit scrubber that it reconfigures
|
||||
# from the tags/attributes on every #sanitize call. Sharing it here would race
|
||||
# our stricter tag set against ActionText's default rendering on concurrent
|
||||
# requests, so use a dedicated instance of the same sanitizer class to keep the
|
||||
# scrubber isolated.
|
||||
def sanitizer
|
||||
sanitizer_class.new
|
||||
end
|
||||
|
||||
def sanitizer_class
|
||||
ActionText::ContentHelper.sanitizer.class
|
||||
end
|
||||
|
||||
def allowed_tags
|
||||
ContentFilters::SanitizeTags::ALLOWED_TAGS
|
||||
end
|
||||
|
||||
def allowed_attributes
|
||||
standard_allowed_attributes + EXTRA_ALLOWED_ATTRIBUTES
|
||||
end
|
||||
|
||||
# Mirrors ActionText::ContentHelper#sanitizer_allowed_attributes, which isn't
|
||||
# exposed at the module level.
|
||||
def standard_allowed_attributes
|
||||
ActionText::ContentHelper.allowed_attributes ||
|
||||
(sanitizer_class.allowed_attributes + ActionText::Attachment::ATTRIBUTES).to_a
|
||||
end
|
||||
end
|
||||
@@ -22,7 +22,7 @@
|
||||
<%= tag.link rel: "apple-touch-icon", href: fresh_account_logo_path %>
|
||||
|
||||
<%= stylesheet_link_tag :all, "data-turbo-track": "reload" %>
|
||||
<%= tag.link rel: "stylesheet", href: fresh_custom_styles_path, data: { turbo_track: "reload" } %>
|
||||
<%= custom_styles_tag %>
|
||||
|
||||
<%= javascript_importmap_tags %>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%# Be sure to check/update messages/_template.html.erb when changing this file %>
|
||||
|
||||
<% cache message do %>
|
||||
<%# Bump this version when the message presentation filters change what they emit. Editing this line changes the template digest, which busts BOTH this fragment cache and the collection cache that keys on this partial's digest (helper Ruby changes alone don't). %>
|
||||
<% cache [ message, "presentation-v2" ] do %>
|
||||
<%= message_tag message do %>
|
||||
<h2 class="message__day-separator"><%= local_datetime_tag message.created_at, style: :date %></h2>
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
||||
pin "@hotwired/turbo-rails", to: "turbo.js"
|
||||
pin "@rails/actioncable", to: "actioncable.esm.js"
|
||||
pin "@rails/request.js", to: "@rails--request.js" # @0.0.8
|
||||
pin "trix", to: "trix.esm.min.js" # @2.1.19
|
||||
pin "trix", to: "trix.js"
|
||||
pin "@rails/actiontext", to: "actiontext.js"
|
||||
pin "highlight.js", to: "highlight.js/core.js"
|
||||
|
||||
|
||||
+1
-5
@@ -21,7 +21,7 @@ Rails.application.routes.draw do
|
||||
|
||||
resource :join_code, only: :create
|
||||
resource :logo, only: %i[ show destroy ]
|
||||
resource :custom_styles, only: %i[ show edit update ]
|
||||
resource :custom_styles, only: %i[ edit update ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,10 +29,6 @@ Rails.application.routes.draw do
|
||||
route_for :account_logo, v: Current.account&.updated_at&.to_fs(:number), size: options[:size]
|
||||
end
|
||||
|
||||
direct :fresh_custom_styles do |options|
|
||||
route_for :account_custom_styles, v: Current.account&.updated_at&.to_fs(:epoch)
|
||||
end
|
||||
|
||||
get "join/:join_code", to: "users#new", as: :join
|
||||
post "join/:join_code", to: "users#create"
|
||||
|
||||
|
||||
@@ -5,34 +5,6 @@ class Accounts::CustomStylesControllerTest < ActionDispatch::IntegrationTest
|
||||
sign_in :david
|
||||
end
|
||||
|
||||
test "show serves custom styles as plain CSS" do
|
||||
accounts(:signal).update! custom_styles: ":root { --color-text: red; }"
|
||||
|
||||
get account_custom_styles_url
|
||||
assert_response :ok
|
||||
assert_equal "text/css", @response.media_type
|
||||
assert_equal ":root { --color-text: red; }", @response.body
|
||||
end
|
||||
|
||||
test "show is accessible without authentication" do
|
||||
accounts(:signal).update! custom_styles: ":root { --color-text: red; }"
|
||||
reset!
|
||||
|
||||
get account_custom_styles_url
|
||||
assert_response :ok
|
||||
assert_equal "text/css", @response.media_type
|
||||
end
|
||||
|
||||
test "show serves markup verbatim as inert CSS text, never HTML" do
|
||||
payload = "</style><script>alert(1)</script>"
|
||||
accounts(:signal).update! custom_styles: payload
|
||||
|
||||
get account_custom_styles_url
|
||||
assert_response :ok
|
||||
assert_equal "text/css", @response.media_type
|
||||
assert_equal payload, @response.body
|
||||
end
|
||||
|
||||
test "edit" do
|
||||
get edit_account_custom_styles_url
|
||||
assert_response :ok
|
||||
|
||||
@@ -61,6 +61,75 @@ class ContentFiltersTest < ActionView::TestCase
|
||||
assert_equal "Hello World", filtered.to_html
|
||||
end
|
||||
|
||||
test "message with a link using an unsafe URI scheme" do
|
||||
message = Message.create! room: rooms(:pets), body: '<div><a href="javascript:alert(1)">x</a></div>', client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_no_match /javascript:/, filtered.to_html
|
||||
assert_match /<a>x<\/a>/, filtered.to_html
|
||||
end
|
||||
|
||||
test "message with an event handler attribute on an allowed tag" do
|
||||
message = Message.create! room: rooms(:pets), body: '<div><a href="/x" onmouseover="alert(1)">x</a> <span onclick="alert(2)">y</span></div>', client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_no_match /onmouseover/, filtered.to_html
|
||||
assert_no_match /onclick/, filtered.to_html
|
||||
assert_match /<a href="\/x">x<\/a>/, filtered.to_html
|
||||
assert_match /<span>y<\/span>/, filtered.to_html
|
||||
end
|
||||
|
||||
test "message with a data URI link" do
|
||||
message = Message.create! room: rooms(:pets), body: '<div><a href="data:text/html,pwned">x</a></div>', client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_no_match /data:/, filtered.to_html
|
||||
assert_match /<a>x<\/a>/, filtered.to_html
|
||||
end
|
||||
|
||||
test "message with a safe link and formatting is preserved" do
|
||||
body = '<div><a href="https://example.com">example</a> <strong>bold</strong> <code>code</code><ul><li>one</li><li>two</li></ul></div>'
|
||||
message = Message.create! room: rooms(:pets), body: body, client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::TextMessagePresentationFilters.apply(message.body.body)
|
||||
assert_match /<a href="https:\/\/example\.com">example<\/a>/, filtered.to_html
|
||||
assert_match /<strong>bold<\/strong>/, filtered.to_html
|
||||
assert_match /<code>code<\/code>/, filtered.to_html
|
||||
assert_match /<ul>\s*<li>one<\/li>\s*<li>two<\/li>\s*<\/ul>/, filtered.to_html
|
||||
end
|
||||
|
||||
test "SanitizeAttributes uses an isolated sanitizer rather than the shared ActionText instance" do
|
||||
filter = ContentFilters::SanitizeAttributes.new(ActionText::Content.new("<div>x</div>"))
|
||||
|
||||
isolated = filter.send(:sanitizer)
|
||||
assert_not_same ActionText::ContentHelper.sanitizer, isolated,
|
||||
"must not reuse the process-wide ActionText sanitizer, whose permit scrubber is mutated per call"
|
||||
assert_not_same isolated, filter.send(:sanitizer),
|
||||
"each call must build a fresh sanitizer so concurrent requests never share scrubber state"
|
||||
assert_kind_of ActionText::ContentHelper.sanitizer.class, isolated
|
||||
end
|
||||
|
||||
test "SanitizeAttributes neutralizes unsafe input and preserves benign content on its isolated sanitizer" do
|
||||
body = <<~HTML.squish
|
||||
<div><a href="javascript:alert(1)" onclick="x()">link</a>
|
||||
<a href="data:text/html,pwned">data</a>
|
||||
<span class="cf-twitter-avatar" onmouseover="y()">avatar</span>
|
||||
<img src="https://evil.example/x.svg"> Hey #{mention_attachment_for(:david)}</div>
|
||||
HTML
|
||||
message = Message.create! room: rooms(:pets), body: body, client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
filtered = ContentFilters::SanitizeAttributes.apply(message.body.body).to_html
|
||||
|
||||
assert_no_match /javascript:/, filtered
|
||||
assert_no_match /data:text\/html/, filtered
|
||||
assert_no_match /onclick/, filtered
|
||||
assert_no_match /onmouseover/, filtered
|
||||
assert_no_match /evil\.example/, filtered
|
||||
assert_match /<span class="cf-twitter-avatar">avatar<\/span>/, filtered
|
||||
assert_match />link</, filtered
|
||||
assert_match /<action-text-attachment sgid="#{users(:david).attachable_sgid}"/, filtered
|
||||
end
|
||||
|
||||
test "message with a mention attachment" do
|
||||
message = Message.create! room: rooms(:pets), body: "<div>Hey #{mention_attachment_for(:david)}</div>", creator: users(:jason)
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
require "test_helper"
|
||||
|
||||
class MessagesHelperTest < ActionView::TestCase
|
||||
test "message_presentation neutralizes unsafe URI schemes in links" do
|
||||
message = Message.create! room: rooms(:pets), body: '<div><a href="javascript:alert(1)">x</a></div>', client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
presentation = view.message_presentation(message)
|
||||
assert_no_match /javascript:/, presentation
|
||||
assert_match /<a>x<\/a>/, presentation
|
||||
end
|
||||
|
||||
test "message_presentation strips event handler attributes from allowed tags" do
|
||||
message = Message.create! room: rooms(:pets), body: '<div><a href="/x" onmouseover="alert(1)">x</a></div>', client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
presentation = view.message_presentation(message)
|
||||
assert_no_match /onmouseover/, presentation
|
||||
assert_match /<a href="\/x">x<\/a>/, presentation
|
||||
end
|
||||
|
||||
test "message_presentation preserves safe links and formatting" do
|
||||
message = Message.create! room: rooms(:pets), body: '<div><a href="https://example.com">example</a> <strong>bold</strong></div>', client_message_id: "0015", creator: users(:jason)
|
||||
|
||||
presentation = view.message_presentation(message)
|
||||
assert_match /<a href="https:\/\/example\.com"[^>]*>example<\/a>/, presentation
|
||||
assert_match /<strong>bold<\/strong>/, presentation
|
||||
end
|
||||
end
|
||||
@@ -1,44 +0,0 @@
|
||||
require "application_system_test_case"
|
||||
|
||||
class CustomStylesXssTest < ApplicationSystemTestCase
|
||||
# A stored payload that breaks out of an inline <style> context and executes
|
||||
# as HTML. Rendered inline (the old behavior) this <script> runs and sets the
|
||||
# flag; delivered as an external text/css stylesheet it is inert text.
|
||||
PAYLOAD = %(</style><script>window.__xss_fired = true</script>)
|
||||
|
||||
setup do
|
||||
accounts(:signal).update!(custom_styles: PAYLOAD)
|
||||
sign_in "jz@37signals.com"
|
||||
end
|
||||
|
||||
test "custom styles payload loads as a stylesheet and never executes" do
|
||||
# Navigate to the page under test explicitly (matching the writebook
|
||||
# sibling) rather than leaning on the post-login landing page — otherwise
|
||||
# this coverage could silently evaporate if that landing changes.
|
||||
visit root_url
|
||||
|
||||
# (a) custom styles arrive via an external stylesheet link, not inline markup
|
||||
assert_selector "link[rel='stylesheet'][href*='custom_styles']", visible: false
|
||||
|
||||
# (b) the payload's <script> never executed — no breakout from CSS context
|
||||
assert_nil evaluate_script("window.__xss_fired")
|
||||
|
||||
# (b') and it injected no live <script> element into the document
|
||||
assert_no_selector "script", text: "__xss_fired", visible: false
|
||||
|
||||
# (c) the browser fetched the payload verbatim as text/css, so it is styled,
|
||||
# never parsed as HTML
|
||||
fetched = page.evaluate_async_script(<<~JS)
|
||||
var done = arguments[arguments.length - 1];
|
||||
var href = document.querySelector("link[rel='stylesheet'][href*='custom_styles']").href;
|
||||
fetch(href).then(function(r) {
|
||||
return r.text().then(function(body) {
|
||||
done({ type: r.headers.get("content-type"), body: body });
|
||||
});
|
||||
});
|
||||
JS
|
||||
|
||||
assert_includes fetched["type"], "text/css"
|
||||
assert_includes fetched["body"], PAYLOAD
|
||||
end
|
||||
end
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
Trix 2.1.19
|
||||
Copyright © 2026 37signals, LLC
|
||||
*/
|
||||
var t="2.1.19";const e="[data-trix-attachment]",i={preview:{presentation:"gallery",caption:{name:!0,size:!0}},file:{caption:{size:!0}}},n={default:{tagName:"div",parse:!1},quote:{tagName:"blockquote",nestable:!0},heading1:{tagName:"h1",terminal:!0,breakOnReturn:!0,group:!1},code:{tagName:"pre",terminal:!0,htmlAttributes:["language"],text:{plaintext:!0}},bulletList:{tagName:"ul",parse:!1},bullet:{tagName:"li",listAttribute:"bulletList",group:!1,nestable:!0,test(t){return r(t.parentNode)===n[this.listAttribute].tagName}},numberList:{tagName:"ol",parse:!1},number:{tagName:"li",listAttribute:"numberList",group:!1,nestable:!0,test(t){return r(t.parentNode)===n[this.listAttribute].tagName}},attachmentGallery:{tagName:"div",exclusive:!0,terminal:!0,parse:!1,group:!1}},r=t=>{var e;return null==t||null===(e=t.tagName)||void 0===e?void 0:e.toLowerCase()},o=navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i),s=o&&parseInt(o[1]);var a={composesExistingText:/Android.*Chrome/.test(navigator.userAgent),recentAndroid:s&&s>12,samsungAndroid:s&&navigator.userAgent.match(/Android.*SM-/),forcesObjectResizing:/Trident.*rv:11/.test(navigator.userAgent),supportsInputEvents:"undefined"!=typeof InputEvent&&["data","getTargetRanges","inputType"].every((t=>t in InputEvent.prototype))},l={ADD_ATTR:["language"],SAFE_FOR_XML:!1,RETURN_DOM:!0},c={attachFiles:"Attach Files",bold:"Bold",bullets:"Bullets",byte:"Byte",bytes:"Bytes",captionPlaceholder:"Add a caption…",code:"Code",heading1:"Heading",indent:"Increase Level",italic:"Italic",link:"Link",numbers:"Numbers",outdent:"Decrease Level",quote:"Quote",redo:"Redo",remove:"Remove",strike:"Strikethrough",undo:"Undo",unlink:"Unlink",url:"URL",urlPlaceholder:"Enter a URL…",GB:"GB",KB:"KB",MB:"MB",PB:"PB",TB:"TB"};const u=[c.bytes,c.KB,c.MB,c.GB,c.TB,c.PB];var h={prefix:"IEC",precision:2,formatter(t){switch(t){case 0:return"0 ".concat(c.bytes);case 1:return"1 ".concat(c.byte);default:let e;"SI"===this.prefix?e=1e3:"IEC"===this.prefix&&(e=1024);const i=Math.floor(Math.log(t)/Math.log(e)),n=(t/Math.pow(e,i)).toFixed(this.precision).replace(/0*$/,"").replace(/\.$/,"");return"".concat(n," ").concat(u[i])}}};const d="\ufeff",g=" ",m=function(t){for(const e in t){const i=t[e];this[e]=i}return this},p=document.documentElement,f=p.matches,b=function(t){let{onElement:e,matchingSelector:i,withCallback:n,inPhase:r,preventDefault:o,times:s}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const a=e||p,l=i,c="capturing"===r,u=function(t){null!=s&&0==--s&&u.destroy();const e=x(t.target,{matchingSelector:l});null!=e&&(null==n||n.call(e,t,e),o&&t.preventDefault())};return u.destroy=()=>a.removeEventListener(t,u,c),a.addEventListener(t,u,c),u},v=function(t){let{bubbles:e,cancelable:i,attributes:n}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e=!1!==e,i=!1!==i;const r=document.createEvent("Events");return r.initEvent(t,e,i),null!=n&&m.call(r,n),r},A=function(t){let{onElement:e,bubbles:i,cancelable:n,attributes:r}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const o=null!=e?e:p,s=v(t,{bubbles:i,cancelable:n,attributes:r});return o.dispatchEvent(s)},y=function(t,e){if(1===(null==t?void 0:t.nodeType))return f.call(t,e)},x=function(t){let{matchingSelector:e,untilNode:i}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};for(;t&&t.nodeType!==Node.ELEMENT_NODE;)t=t.parentNode;if(null!=t){if(null==e)return t;if(t.closest&&null==i)return t.closest(e);for(;t&&t!==i;){if(y(t,e))return t;t=t.parentNode}}},C=t=>document.activeElement!==t&&E(t,document.activeElement),E=function(t,e){if(t&&e)for(;e;){if(e===t)return!0;e=e.parentNode}},S=function(t){var e;if(null===(e=t)||void 0===e||!e.parentNode)return;let i=0;for(t=t.previousSibling;t;)i++,t=t.previousSibling;return i},R=t=>{var e;return null==t||null===(e=t.parentNode)||void 0===e?void 0:e.removeChild(t)},T=function(t){let{onlyNodesOfType:e,usingFilter:i,expandEntityReferences:n}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const r=(()=>{switch(e){case"element":return NodeFilter.SHOW_ELEMENT;case"text":return NodeFilter.SHOW_TEXT;case"comment":return NodeFilter.SHOW_COMMENT;default:return NodeFilter.SHOW_ALL}})();return document.createTreeWalker(t,r,null!=i?i:null,!0===n)},k=t=>{var e;return null==t||null===(e=t.tagName)||void 0===e?void 0:e.toLowerCase()},w=function(t){let e,i,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"object"==typeof t?(n=t,t=n.tagName):n={attributes:n};const r=document.createElement(t);if(null!=n.editable&&(null==n.attributes&&(n.attributes={}),n.attributes.contenteditable=n.editable),n.attributes)for(e in n.attributes)i=n.attributes[e],r.setAttribute(e,i);if(n.style)for(e in n.style)i=n.style[e],r.style[e]=i;if(n.data)for(e in n.data)i=n.data[e],r.dataset[e]=i;return n.className&&n.className.split(" ").forEach((t=>{r.classList.add(t)})),n.textContent&&(r.textContent=n.textContent),n.childNodes&&[].concat(n.childNodes).forEach((t=>{r.appendChild(t)})),r};let D;const L=funLine truncated
|
||||
/*! @license DOMPurify 3.4.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.2/LICENSE */const{entries:oe,setPrototypeOf:se,isFrozen:ae,getPrototypeOf:le,getOwnPropertyDescriptor:ce}=Object;let{freeze:ue,seal:he,create:de}=Object,{apply:ge,construct:me}="undefined"!=typeof Reflect&&Reflect;ue||(ue=function(t){return t}),he||(he=function(t){return t}),ge||(ge=function(t,e){for(var i=arguments.length,n=new Array(i>2?i-2:0),r=2;r<i;r++)n[r-2]=arguments[r];return t.apply(e,n)}),me||(me=function(t){for(var e=arguments.length,i=new Array(e>1?e-1:0),n=1;n<e;n++)i[n-1]=arguments[n];return new t(...i)});const pe=Me(Array.prototype.forEach),fe=Me(Array.prototype.lastIndexOf),be=Me(Array.prototype.pop),ve=Me(Array.prototype.push),Ae=Me(Array.prototype.splice),ye=Array.isArray,xe=Me(String.prototype.toLowerCase),Ce=Me(String.prototype.toString),Ee=Me(String.prototype.match),Se=Me(String.prototype.replace),Re=Me(String.prototype.indexOf),Te=Me(String.prototype.trim),ke=Me(Number.prototype.toString),we=Me(Boolean.prototype.toString),De="undefined"==typeof BigInt?null:Me(BigInt.prototype.toString),Le="undefined"==typeof Symbol?null:Me(Symbol.prototype.toString),Ne=Me(Object.prototype.hasOwnProperty),Ie=Me(Object.prototype.toString),Oe=Me(RegExp.prototype.test),Fe=(Pe=TypeError,function(){for(var t=arguments.length,e=new Array(t),i=0;i<t;i++)e[i]=arguments[i];return me(Pe,e)});var Pe;function Me(t){return function(e){e instanceof RegExp&&(e.lastIndex=0);for(var i=arguments.length,n=new Array(i>1?i-1:0),r=1;r<i;r++)n[r-1]=arguments[r];return ge(t,e,n)}}function Be(t,e){let i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:xe;if(se&&se(t,null),!ye(e))return t;let n=e.length;for(;n--;){let r=e[n];if("string"==typeof r){const t=i(r);t!==r&&(ae(e)||(e[n]=t),r=t)}t[r]=!0}return t}function _e(t){for(let e=0;e<t.length;e++){Ne(t,e)||(t[e]=null)}return t}function je(t){const e=de(null);for(const[i,n]of oe(t)){Ne(t,i)&&(ye(n)?e[i]=_e(n):n&&"object"==typeof n&&n.constructor===Object?e[i]=je(n):e[i]=n)}return e}function We(t,e){for(;null!==t;){const i=ce(t,e);if(i){if(i.get)return Me(i.get);if("function"==typeof i.value)return Me(i.value)}t=le(t)}return function(){return null}}const Ue=ue(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),Ve=ue(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),ze=ue(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),He=ue(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),qe=ue(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),Je=ue(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),Ke=ue(["#text"]),Ge=ue(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","contLine truncated
|
||||
//# sourceMappingURL=trix.esm.min.js.map
|
||||
Reference in new issue
Block a user