panda-cms 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +73 -0
- data/Rakefile +7 -0
- data/app/assets/builds/panda.cms.css +2808 -0
- data/app/assets/config/panda_cms_manifest.js +4 -0
- data/app/assets/stylesheets/panda/cms/application.tailwind.css +162 -0
- data/app/assets/stylesheets/panda/cms/editor.css +120 -0
- data/app/builders/panda/cms/form_builder.rb +234 -0
- data/app/components/panda/cms/admin/button_component.rb +70 -0
- data/app/components/panda/cms/admin/container_component.html.erb +13 -0
- data/app/components/panda/cms/admin/container_component.rb +13 -0
- data/app/components/panda/cms/admin/flash_message_component.html.erb +31 -0
- data/app/components/panda/cms/admin/flash_message_component.rb +47 -0
- data/app/components/panda/cms/admin/heading_component.rb +45 -0
- data/app/components/panda/cms/admin/panel_component.html.erb +7 -0
- data/app/components/panda/cms/admin/panel_component.rb +13 -0
- data/app/components/panda/cms/admin/slideover_component.html.erb +9 -0
- data/app/components/panda/cms/admin/slideover_component.rb +15 -0
- data/app/components/panda/cms/admin/statistics_component.html.erb +4 -0
- data/app/components/panda/cms/admin/statistics_component.rb +17 -0
- data/app/components/panda/cms/admin/tab_bar_component.html.erb +35 -0
- data/app/components/panda/cms/admin/tab_bar_component.rb +15 -0
- data/app/components/panda/cms/admin/table_component.html.erb +29 -0
- data/app/components/panda/cms/admin/table_component.rb +46 -0
- data/app/components/panda/cms/admin/tag_component.rb +35 -0
- data/app/components/panda/cms/admin/user_activity_component.html.erb +5 -0
- data/app/components/panda/cms/admin/user_activity_component.rb +33 -0
- data/app/components/panda/cms/admin/user_display_component.html.erb +17 -0
- data/app/components/panda/cms/admin/user_display_component.rb +21 -0
- data/app/components/panda/cms/code_component.rb +64 -0
- data/app/components/panda/cms/grid_component.html.erb +6 -0
- data/app/components/panda/cms/grid_component.rb +15 -0
- data/app/components/panda/cms/menu_component.html.erb +6 -0
- data/app/components/panda/cms/menu_component.rb +58 -0
- data/app/components/panda/cms/page_menu_component.html.erb +21 -0
- data/app/components/panda/cms/page_menu_component.rb +38 -0
- data/app/components/panda/cms/rich_text_component.html.erb +6 -0
- data/app/components/panda/cms/rich_text_component.rb +84 -0
- data/app/components/panda/cms/text_component.rb +72 -0
- data/app/constraints/panda/cms/admin_constraint.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +52 -0
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +20 -0
- data/app/controllers/panda/cms/admin/files_controller.rb +21 -0
- data/app/controllers/panda/cms/admin/forms_controller.rb +53 -0
- data/app/controllers/panda/cms/admin/menus_controller.rb +30 -0
- data/app/controllers/panda/cms/admin/pages_controller.rb +91 -0
- data/app/controllers/panda/cms/admin/posts_controller.rb +146 -0
- data/app/controllers/panda/cms/admin/sessions_controller.rb +94 -0
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +37 -0
- data/app/controllers/panda/cms/admin/settings_controller.rb +20 -0
- data/app/controllers/panda/cms/application_controller.rb +57 -0
- data/app/controllers/panda/cms/errors_controller.rb +33 -0
- data/app/controllers/panda/cms/form_submissions_controller.rb +23 -0
- data/app/controllers/panda/cms/pages_controller.rb +72 -0
- data/app/controllers/panda/cms/posts_controller.rb +13 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +6 -0
- data/app/helpers/panda/cms/admin/pages_helper.rb +6 -0
- data/app/helpers/panda/cms/admin/posts_helper.rb +48 -0
- data/app/helpers/panda/cms/application_helper.rb +120 -0
- data/app/helpers/panda/cms/pages_helper.rb +6 -0
- data/app/helpers/panda/cms/theme_helper.rb +18 -0
- data/app/javascript/panda/cms/@editorjs--editorjs.js +2577 -0
- data/app/javascript/panda/cms/@hotwired--stimulus.js +4 -0
- data/app/javascript/panda/cms/@hotwired--turbo.js +160 -0
- data/app/javascript/panda/cms/@rails--actioncable--src.js +4 -0
- data/app/javascript/panda/cms/application_panda_cms.js +39 -0
- data/app/javascript/panda/cms/controllers/dashboard_controller.js +7 -0
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +77 -0
- data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +320 -0
- data/app/javascript/panda/cms/controllers/index.js +48 -0
- data/app/javascript/panda/cms/controllers/slug_controller.js +87 -0
- data/app/javascript/panda/cms/editor/css_extractor.js +80 -0
- data/app/javascript/panda/cms/editor/editor_js_config.js +177 -0
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +285 -0
- data/app/javascript/panda/cms/editor/plain_text_editor.js +110 -0
- data/app/javascript/panda/cms/editor/resource_loader.js +115 -0
- data/app/javascript/panda/cms/tailwindcss-stimulus-components.js +4 -0
- data/app/jobs/panda/cms/application_job.rb +6 -0
- data/app/jobs/panda/cms/record_visit_job.rb +31 -0
- data/app/mailers/panda/cms/application_mailer.rb +8 -0
- data/app/mailers/panda/cms/form_mailer.rb +21 -0
- data/app/models/action_text/rich_text_version.rb +6 -0
- data/app/models/panda/cms/application_record.rb +7 -0
- data/app/models/panda/cms/block.rb +34 -0
- data/app/models/panda/cms/block_content.rb +18 -0
- data/app/models/panda/cms/block_content_version.rb +8 -0
- data/app/models/panda/cms/breadcrumb.rb +12 -0
- data/app/models/panda/cms/current.rb +17 -0
- data/app/models/panda/cms/form.rb +9 -0
- data/app/models/panda/cms/form_submission.rb +7 -0
- data/app/models/panda/cms/menu.rb +52 -0
- data/app/models/panda/cms/menu_item.rb +58 -0
- data/app/models/panda/cms/page.rb +96 -0
- data/app/models/panda/cms/page_version.rb +8 -0
- data/app/models/panda/cms/post.rb +60 -0
- data/app/models/panda/cms/post_version.rb +8 -0
- data/app/models/panda/cms/redirect.rb +11 -0
- data/app/models/panda/cms/template.rb +124 -0
- data/app/models/panda/cms/template_version.rb +8 -0
- data/app/models/panda/cms/user.rb +31 -0
- data/app/models/panda/cms/version.rb +8 -0
- data/app/models/panda/cms/visit.rb +9 -0
- data/app/services/panda/cms/html_to_editor_js_converter.rb +200 -0
- data/app/views/active_storage/blobs/blobs/_blob.html.erb +14 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/panda/cms/application.html.erb +41 -0
- data/app/views/layouts/panda/cms/public.html.erb +3 -0
- data/app/views/panda/cms/admin/dashboard/show.html.erb +12 -0
- data/app/views/panda/cms/admin/files/index.html.erb +124 -0
- data/app/views/panda/cms/admin/files/show.html.erb +2 -0
- data/app/views/panda/cms/admin/forms/edit.html.erb +0 -0
- data/app/views/panda/cms/admin/forms/index.html.erb +13 -0
- data/app/views/panda/cms/admin/forms/new.html.erb +15 -0
- data/app/views/panda/cms/admin/forms/show.html.erb +35 -0
- data/app/views/panda/cms/admin/menus/index.html.erb +8 -0
- data/app/views/panda/cms/admin/pages/edit.html.erb +36 -0
- data/app/views/panda/cms/admin/pages/index.html.erb +22 -0
- data/app/views/panda/cms/admin/pages/new.html.erb +15 -0
- data/app/views/panda/cms/admin/pages/show.html.erb +1 -0
- data/app/views/panda/cms/admin/posts/_form.html.erb +29 -0
- data/app/views/panda/cms/admin/posts/edit.html.erb +6 -0
- data/app/views/panda/cms/admin/posts/index.html.erb +18 -0
- data/app/views/panda/cms/admin/posts/new.html.erb +6 -0
- data/app/views/panda/cms/admin/sessions/new.html.erb +17 -0
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +68 -0
- data/app/views/panda/cms/admin/settings/index.html.erb +21 -0
- data/app/views/panda/cms/admin/settings/insta.html +4 -0
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +28 -0
- data/app/views/panda/cms/admin/shared/_flash.html.erb +5 -0
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +41 -0
- data/app/views/panda/cms/form_mailer/notification_email.html.erb +11 -0
- data/app/views/panda/cms/shared/_editor.html.erb +0 -0
- data/app/views/panda/cms/shared/_favicons.html.erb +9 -0
- data/app/views/panda/cms/shared/_footer.html.erb +2 -0
- data/app/views/panda/cms/shared/_header.html.erb +15 -0
- data/app/views/panda/cms/shared/_importmap.html.erb +33 -0
- data/config/importmap.rb +13 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/initializers/panda/cms/form_errors.rb +38 -0
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +11 -0
- data/config/initializers/panda/cms/paper_trail.rb +7 -0
- data/config/initializers/panda/cms.rb +10 -0
- data/config/initializers/zeitwork.rb +3 -0
- data/config/locales/en.yml +49 -0
- data/config/puma/test.rb +9 -0
- data/config/routes.rb +48 -0
- data/config/tailwind.config.js +37 -0
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
- data/db/migrate/20240305000000_convert_html_content_to_editor_js.rb +82 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +9 -0
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
- data/db/migrate/20240904200605_create_action_text_tables.action_text.rb +24 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +11 -0
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +5 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +35 -0
- data/db/migrate/20241119214549_remove_action_text_from_posts.rb +9 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +19 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +27 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +5 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +5 -0
- data/db/migrate/migrate +1 -0
- data/db/seeds.rb +5 -0
- data/lib/generators/panda/cms/install_generator.rb +29 -0
- data/lib/panda/cms/bulk_editor.rb +171 -0
- data/lib/panda/cms/demo_site_generator.rb +67 -0
- data/lib/panda/cms/editor_js/blocks/alert.rb +34 -0
- data/lib/panda/cms/editor_js/blocks/base.rb +33 -0
- data/lib/panda/cms/editor_js/blocks/header.rb +15 -0
- data/lib/panda/cms/editor_js/blocks/image.rb +36 -0
- data/lib/panda/cms/editor_js/blocks/list.rb +32 -0
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +15 -0
- data/lib/panda/cms/editor_js/blocks/quote.rb +41 -0
- data/lib/panda/cms/editor_js/blocks/table.rb +50 -0
- data/lib/panda/cms/editor_js/renderer.rb +124 -0
- data/lib/panda/cms/editor_js.rb +16 -0
- data/lib/panda/cms/editor_js_content.rb +21 -0
- data/lib/panda/cms/engine.rb +257 -0
- data/lib/panda/cms/exceptions_app.rb +26 -0
- data/lib/panda/cms/railtie.rb +11 -0
- data/lib/panda/cms/slug.rb +24 -0
- data/lib/panda/cms.rb +0 -0
- data/lib/panda-cms/version.rb +5 -0
- data/lib/panda-cms.rb +81 -0
- data/lib/tasks/panda_cms.rake +54 -0
- data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
- data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
- data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
- data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
- data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
- data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
- data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
- data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
- data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
- data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
- data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
- data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
- data/public/panda-cms-assets/favicons/favicon.ico +0 -0
- data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
- data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
- data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
- data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
- data/public/panda-cms-assets/panda-nav.png +0 -0
- data/public/panda-cms-assets/rich_text_editor.css +568 -0
- metadata +654 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["editorContainer", "hiddenField"];
|
|
5
|
+
static values = {
|
|
6
|
+
editorId: String,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.initializeEditor();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async initializeEditor() {
|
|
14
|
+
if (this.editor) return;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const holderId =
|
|
18
|
+
this.editorIdValue + "_holder" ||
|
|
19
|
+
`editor-${Math.random().toString(36).substring(2, 9)}`;
|
|
20
|
+
let holderDiv = document.createElement("div");
|
|
21
|
+
holderDiv.id = holderId;
|
|
22
|
+
holderDiv.className = "codex-editor";
|
|
23
|
+
this.editorContainerTarget.innerHTML = "";
|
|
24
|
+
this.editorContainerTarget.appendChild(holderDiv);
|
|
25
|
+
|
|
26
|
+
const { getEditorConfig } = await import(
|
|
27
|
+
"panda/cms/editor/editor_js_config"
|
|
28
|
+
);
|
|
29
|
+
const config = getEditorConfig(holderId, this.getInitialContent());
|
|
30
|
+
|
|
31
|
+
editor_content_post;
|
|
32
|
+
|
|
33
|
+
this.editor = new EditorJS({
|
|
34
|
+
...config,
|
|
35
|
+
holder: holderId,
|
|
36
|
+
autofocus: false,
|
|
37
|
+
minHeight: 1,
|
|
38
|
+
logLevel: "ERROR",
|
|
39
|
+
onChange: () => {
|
|
40
|
+
if (!this.editor) return;
|
|
41
|
+
this.editor.save().then((outputData) => {
|
|
42
|
+
outputData.source = "editorJS";
|
|
43
|
+
this.hiddenFieldTarget.value = JSON.stringify(outputData);
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("[Panda CMS] Editor setup failed:", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getInitialContent() {
|
|
53
|
+
try {
|
|
54
|
+
const value = this.hiddenFieldTarget.value;
|
|
55
|
+
if (value && value !== "{}") {
|
|
56
|
+
const data = JSON.parse(value);
|
|
57
|
+
if (data.blocks) return data;
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn("[Panda CMS] Could not parse initial content:", e);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
time: Date.now(),
|
|
65
|
+
blocks: [{ type: "paragraph", data: { text: "" } }],
|
|
66
|
+
version: "2.28.2",
|
|
67
|
+
source: "editorJS",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
disconnect() {
|
|
72
|
+
if (this.editor) {
|
|
73
|
+
this.editor.destroy();
|
|
74
|
+
this.editor = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { PlainTextEditor } from "panda/cms/editor/plain_text_editor"
|
|
3
|
+
import { EditorJSInitializer } from "panda/cms/editor/editor_js_initializer"
|
|
4
|
+
|
|
5
|
+
export default class extends Controller {
|
|
6
|
+
static values = {
|
|
7
|
+
pageId: Number,
|
|
8
|
+
adminPath: String,
|
|
9
|
+
autosave: Boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
connect() {
|
|
13
|
+
console.debug("[Panda CMS] EditorIframe controller connected")
|
|
14
|
+
this.frame = this.element
|
|
15
|
+
this.setupControls()
|
|
16
|
+
this.setupFrame()
|
|
17
|
+
this.editors = []
|
|
18
|
+
this.editorsInitialized = {
|
|
19
|
+
plain: false,
|
|
20
|
+
rich: false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setupControls() {
|
|
25
|
+
// Create editor controls if they don't exist
|
|
26
|
+
if (!parent.document.querySelector('.editor-controls')) {
|
|
27
|
+
const controls = parent.document.createElement('div')
|
|
28
|
+
controls.className = 'editor-controls'
|
|
29
|
+
parent.document.body.appendChild(controls)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create save button if it doesn't exist
|
|
33
|
+
if (!parent.document.getElementById('saveEditableButton')) {
|
|
34
|
+
const saveButton = parent.document.createElement('a')
|
|
35
|
+
saveButton.id = 'saveEditableButton'
|
|
36
|
+
saveButton.href = '#'
|
|
37
|
+
saveButton.textContent = 'Save Changes'
|
|
38
|
+
saveButton.className = 'btn btn-primary'
|
|
39
|
+
parent.document.querySelector('.editor-controls').appendChild(saveButton)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setupFrame() {
|
|
44
|
+
// Always show the frame initially to ensure it's visible for tests
|
|
45
|
+
this.frame.style.display = ""
|
|
46
|
+
this.frame.style.width = "100%"
|
|
47
|
+
this.frame.style.height = "100%"
|
|
48
|
+
this.frame.style.minHeight = "500px"
|
|
49
|
+
|
|
50
|
+
// Get CSRF token
|
|
51
|
+
this.csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || ""
|
|
52
|
+
|
|
53
|
+
// Setup frame load handler
|
|
54
|
+
this.frame.addEventListener("load", async () => {
|
|
55
|
+
console.debug("[Panda CMS] Frame loaded")
|
|
56
|
+
this.frameDocument = this.frame.contentDocument || this.frame.contentWindow.document
|
|
57
|
+
this.body = this.frameDocument.body
|
|
58
|
+
this.head = this.frameDocument.head
|
|
59
|
+
|
|
60
|
+
// Set up error handling for the iframe
|
|
61
|
+
this.frameDocument.defaultView.onerror = (message, source, lineno, colno, error) => {
|
|
62
|
+
// Relay the error to the parent window
|
|
63
|
+
const fullMessage = `iFrame Error: ${message} (${source}:${lineno}:${colno})`
|
|
64
|
+
console.error(fullMessage, error)
|
|
65
|
+
|
|
66
|
+
// Throw the error in the parent context for Cuprite to catch
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
throw new Error(fullMessage)
|
|
69
|
+
}, 0)
|
|
70
|
+
|
|
71
|
+
return false // Let the error propagate
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Set up unhandled rejection handling for the iframe
|
|
75
|
+
this.frameDocument.defaultView.onunhandledrejection = (event) => {
|
|
76
|
+
const fullMessage = `iFrame Unhandled Promise Rejection: ${event.reason}`
|
|
77
|
+
console.error(fullMessage)
|
|
78
|
+
|
|
79
|
+
// Throw the error in the parent context for Cuprite to catch
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
throw event.reason
|
|
82
|
+
}, 0)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Ensure frame is visible after load
|
|
86
|
+
this.frame.style.display = ""
|
|
87
|
+
this.ensureFrameVisibility()
|
|
88
|
+
|
|
89
|
+
// Wait for document to be ready
|
|
90
|
+
if (this.frameDocument.readyState !== 'complete') {
|
|
91
|
+
await new Promise(resolve => {
|
|
92
|
+
this.frameDocument.addEventListener('DOMContentLoaded', resolve)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Load Editor.js resources in the iframe context
|
|
97
|
+
try {
|
|
98
|
+
const { EDITOR_JS_RESOURCES, EDITOR_JS_CSS } = await import("panda/cms/editor/editor_js_config")
|
|
99
|
+
const { ResourceLoader } = await import("panda/cms/editor/resource_loader")
|
|
100
|
+
|
|
101
|
+
// First load EditorJS core
|
|
102
|
+
const editorCore = EDITOR_JS_RESOURCES[0]
|
|
103
|
+
await ResourceLoader.loadScript(this.frameDocument, this.head, editorCore)
|
|
104
|
+
|
|
105
|
+
// Then load all tools in parallel
|
|
106
|
+
const toolLoads = EDITOR_JS_RESOURCES.slice(1).map(async (resource) => {
|
|
107
|
+
await ResourceLoader.loadScript(this.frameDocument, this.head, resource)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// Load CSS directly
|
|
111
|
+
await ResourceLoader.embedCSS(this.frameDocument, this.head, EDITOR_JS_CSS)
|
|
112
|
+
|
|
113
|
+
// Wait for all resources to load
|
|
114
|
+
await Promise.all(toolLoads)
|
|
115
|
+
console.debug("[Panda CMS] Editor resources loaded in iframe")
|
|
116
|
+
|
|
117
|
+
// Wait a small amount of time for scripts to initialize
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
119
|
+
|
|
120
|
+
// Initialize editors only if we have the body and editable elements
|
|
121
|
+
if (this.body && this.body.querySelector('[data-editable-kind]')) {
|
|
122
|
+
await this.initializeEditors()
|
|
123
|
+
} else {
|
|
124
|
+
const error = new Error("[Panda CMS] Frame body or editable elements not found")
|
|
125
|
+
console.error(error)
|
|
126
|
+
throw error
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("[Panda CMS] Error loading editor resources in iframe:", error)
|
|
130
|
+
throw error
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ensureFrameVisibility() {
|
|
136
|
+
// Force frame to be visible
|
|
137
|
+
this.frame.style.display = ""
|
|
138
|
+
|
|
139
|
+
// Check dimensions and fix if needed
|
|
140
|
+
if (this.frame.offsetWidth === 0 || this.frame.offsetHeight === 0) {
|
|
141
|
+
console.warn("[Panda CMS] iFrame has zero dimensions, fixing...")
|
|
142
|
+
this.frame.style.width = "100%"
|
|
143
|
+
this.frame.style.height = "100%"
|
|
144
|
+
this.frame.style.minHeight = "500px"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Log visibility state
|
|
148
|
+
console.debug("[Panda CMS] Frame visibility state:", {
|
|
149
|
+
display: this.frame.style.display,
|
|
150
|
+
width: this.frame.offsetWidth,
|
|
151
|
+
height: this.frame.offsetHeight,
|
|
152
|
+
visible: this.frame.offsetParent !== null
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
initializeEditors() {
|
|
157
|
+
console.debug("[Panda CMS] Starting editor initialization")
|
|
158
|
+
|
|
159
|
+
// Get all editable elements
|
|
160
|
+
const plainTextElements = this.body.querySelectorAll('[data-editable-kind="plain_text"], [data-editable-kind="markdown"], [data-editable-kind="html"]')
|
|
161
|
+
const richTextElements = this.body.querySelectorAll('[data-editable-kind="rich_text"]')
|
|
162
|
+
|
|
163
|
+
console.debug(`[Panda CMS] Found ${plainTextElements.length} plain text elements and ${richTextElements.length} rich text elements`)
|
|
164
|
+
|
|
165
|
+
// Always ensure frame is visible
|
|
166
|
+
this.ensureFrameVisibility()
|
|
167
|
+
|
|
168
|
+
// Initialize editors if they exist
|
|
169
|
+
if (plainTextElements.length > 0 || richTextElements.length > 0) {
|
|
170
|
+
this.initializePlainTextEditors()
|
|
171
|
+
this.initializeRichTextEditors()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
initializePlainTextEditors() {
|
|
176
|
+
this.editorsInitialized.plain = false
|
|
177
|
+
const plainTextElements = this.body.querySelectorAll('[data-editable-kind="plain_text"], [data-editable-kind="markdown"], [data-editable-kind="html"]')
|
|
178
|
+
console.debug(`[Panda CMS] Found ${plainTextElements.length} plain text elements`)
|
|
179
|
+
|
|
180
|
+
plainTextElements.forEach(element => {
|
|
181
|
+
const editor = new PlainTextEditor(element, this.frameDocument, {
|
|
182
|
+
autosave: this.autosaveValue,
|
|
183
|
+
adminPath: this.adminPathValue,
|
|
184
|
+
csrfToken: this.csrfToken
|
|
185
|
+
})
|
|
186
|
+
this.editors.push(editor)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
this.editorsInitialized.plain = true
|
|
190
|
+
this.checkAllEditorsInitialized()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async initializeRichTextEditors() {
|
|
194
|
+
this.editorsInitialized.rich = false
|
|
195
|
+
const richTextElements = this.body.querySelectorAll('[data-editable-kind="rich_text"]')
|
|
196
|
+
console.debug(`[Panda CMS] Found ${richTextElements.length} rich text elements`)
|
|
197
|
+
|
|
198
|
+
if (richTextElements.length > 0) {
|
|
199
|
+
// Verify Editor.js is available in the iframe context
|
|
200
|
+
if (!this.frameDocument.defaultView.EditorJS) {
|
|
201
|
+
const error = new Error("Editor.js not loaded in iframe context")
|
|
202
|
+
console.error("[Panda CMS]", error)
|
|
203
|
+
throw error // This will bubble up and fail the test
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const initializer = new EditorJSInitializer(this.frameDocument, true)
|
|
207
|
+
|
|
208
|
+
// Don't wrap in try/catch to let errors bubble up
|
|
209
|
+
const editors = await Promise.all(
|
|
210
|
+
Array.from(richTextElements).map(async element => {
|
|
211
|
+
// Create holder element before initialization
|
|
212
|
+
const holderId = `editor-${Math.random().toString(36).substr(2, 9)}`
|
|
213
|
+
const holderElement = this.frameDocument.createElement('div')
|
|
214
|
+
holderElement.id = holderId
|
|
215
|
+
holderElement.className = 'editor-js-holder codex-editor'
|
|
216
|
+
element.appendChild(holderElement)
|
|
217
|
+
|
|
218
|
+
// Wait for the holder element to be in the DOM
|
|
219
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
220
|
+
|
|
221
|
+
// Verify the holder element exists
|
|
222
|
+
const verifyHolder = this.frameDocument.getElementById(holderId)
|
|
223
|
+
if (!verifyHolder) {
|
|
224
|
+
const error = new Error(`Failed to create editor holder element ${holderId}`)
|
|
225
|
+
console.error("[Panda CMS]", error)
|
|
226
|
+
throw error // This will bubble up and fail the test
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.debug(`[Panda CMS] Created editor holder: ${holderId}`, {
|
|
230
|
+
exists: !!verifyHolder,
|
|
231
|
+
parent: element.id || 'no-id',
|
|
232
|
+
editorJSAvailable: !!this.frameDocument.defaultView.EditorJS
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Initialize editor with empty data
|
|
236
|
+
const editor = await initializer.initialize(holderElement, {}, holderId)
|
|
237
|
+
|
|
238
|
+
// Set up save handler for this editor
|
|
239
|
+
const saveButton = parent.document.getElementById('saveEditableButton')
|
|
240
|
+
if (saveButton) {
|
|
241
|
+
saveButton.addEventListener('click', async () => {
|
|
242
|
+
const outputData = await editor.save()
|
|
243
|
+
outputData.source = "editorJS"
|
|
244
|
+
|
|
245
|
+
const pageId = element.getAttribute("data-editable-page-id")
|
|
246
|
+
const blockContentId = element.getAttribute("data-editable-block-content-id")
|
|
247
|
+
|
|
248
|
+
const response = await fetch(`${this.adminPathValue}/pages/${pageId}/block_contents/${blockContentId}`, {
|
|
249
|
+
method: "PATCH",
|
|
250
|
+
headers: {
|
|
251
|
+
"Content-Type": "application/json",
|
|
252
|
+
"X-CSRF-Token": this.csrfToken
|
|
253
|
+
},
|
|
254
|
+
body: JSON.stringify({ content: outputData })
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
const error = new Error('Save failed')
|
|
259
|
+
console.error("[Panda CMS]", error)
|
|
260
|
+
throw error
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.handleSuccess()
|
|
264
|
+
})
|
|
265
|
+
} else {
|
|
266
|
+
console.warn("[Panda CMS] Save button not found")
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return editor
|
|
270
|
+
})
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
// Filter out any null editors and add the valid ones
|
|
274
|
+
const validEditors = editors.filter(editor => editor !== null)
|
|
275
|
+
this.editors.push(...validEditors)
|
|
276
|
+
|
|
277
|
+
// If we didn't get any valid editors, that's an error
|
|
278
|
+
if (validEditors.length === 0) {
|
|
279
|
+
const error = new Error("No editors were successfully initialized")
|
|
280
|
+
console.error("[Panda CMS]", error)
|
|
281
|
+
throw error
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.editorsInitialized.rich = true
|
|
286
|
+
this.checkAllEditorsInitialized()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
checkAllEditorsInitialized() {
|
|
290
|
+
console.log("[Panda CMS] Editor initialization status:", this.editorsInitialized)
|
|
291
|
+
|
|
292
|
+
// Always ensure frame is visible
|
|
293
|
+
this.ensureFrameVisibility()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
handleError(error) {
|
|
297
|
+
const errorMessage = parent.document.getElementById("errorMessage")
|
|
298
|
+
if (errorMessage) {
|
|
299
|
+
errorMessage.getElementsByClassName('flash-message-text')[0].textContent = error
|
|
300
|
+
errorMessage.classList.remove("hidden")
|
|
301
|
+
setTimeout(() => {
|
|
302
|
+
errorMessage.classList.add("hidden")
|
|
303
|
+
}, 3000)
|
|
304
|
+
}
|
|
305
|
+
console.error("[Panda CMS] Error:", error)
|
|
306
|
+
|
|
307
|
+
// Throw the error to fail the test
|
|
308
|
+
throw error
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
handleSuccess() {
|
|
312
|
+
const successMessage = parent.document.getElementById("successMessage")
|
|
313
|
+
if (successMessage) {
|
|
314
|
+
successMessage.classList.remove("hidden")
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
successMessage.classList.add("hidden")
|
|
317
|
+
}, 3000)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
console.debug("[Panda CMS] Importing Panda CMS Stimulus Controller...")
|
|
2
|
+
|
|
3
|
+
import { Application as PandaCMSApplication } from "@hotwired/stimulus"
|
|
4
|
+
|
|
5
|
+
const pandaCmsApplication = PandaCMSApplication.start()
|
|
6
|
+
|
|
7
|
+
console.debug("[Panda CMS] Application started...")
|
|
8
|
+
|
|
9
|
+
// Configure Stimulus development experience
|
|
10
|
+
pandaCmsApplication.debug = false
|
|
11
|
+
window.pandaCmsStimulus = pandaCmsApplication
|
|
12
|
+
|
|
13
|
+
console.debug("[Panda CMS] window.pandaCmsStimulus available...")
|
|
14
|
+
|
|
15
|
+
console.debug("[Panda CMS] Registering controllers...")
|
|
16
|
+
|
|
17
|
+
// Use the same paths as defined in _importmap.html.erb
|
|
18
|
+
import DashboardController from "panda/cms/controllers/dashboard_controller"
|
|
19
|
+
pandaCmsApplication.register("dashboard", DashboardController)
|
|
20
|
+
|
|
21
|
+
import EditorFormController from "panda/cms/controllers/editor_form_controller"
|
|
22
|
+
pandaCmsApplication.register("editor-form", EditorFormController)
|
|
23
|
+
|
|
24
|
+
import SlugController from "panda/cms/controllers/slug_controller"
|
|
25
|
+
pandaCmsApplication.register("slug", SlugController)
|
|
26
|
+
|
|
27
|
+
import EditorIframeController from "panda/cms/controllers/editor_iframe_controller"
|
|
28
|
+
pandaCmsApplication.register("editor-iframe", EditorIframeController)
|
|
29
|
+
|
|
30
|
+
console.debug("[Panda CMS] Registering components...")
|
|
31
|
+
|
|
32
|
+
// Import and register all TailwindCSS Components or just the ones you need
|
|
33
|
+
import { Alert, Autosave, ColorPreview, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
|
|
34
|
+
pandaCmsApplication.register('alert', Alert)
|
|
35
|
+
pandaCmsApplication.register('autosave', Autosave)
|
|
36
|
+
pandaCmsApplication.register('color-preview', ColorPreview)
|
|
37
|
+
pandaCmsApplication.register('dropdown', Dropdown)
|
|
38
|
+
pandaCmsApplication.register('modal', Modal)
|
|
39
|
+
pandaCmsApplication.register('popover', Popover)
|
|
40
|
+
pandaCmsApplication.register('slideover', Slideover)
|
|
41
|
+
pandaCmsApplication.register('tabs', Tabs)
|
|
42
|
+
pandaCmsApplication.register('toggle', Toggle)
|
|
43
|
+
|
|
44
|
+
console.debug("[Panda CMS] Components registered...")
|
|
45
|
+
|
|
46
|
+
export { pandaCmsApplication }
|
|
47
|
+
|
|
48
|
+
console.debug("[Panda CMS] Application exported...")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = [
|
|
5
|
+
"existing_root",
|
|
6
|
+
"input_select",
|
|
7
|
+
"input_text",
|
|
8
|
+
"output_text",
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
console.debug("[Panda CMS] Slug handler connected...");
|
|
13
|
+
// Generate path on initial load if title exists
|
|
14
|
+
if (this.input_textTarget.value) {
|
|
15
|
+
this.generatePath();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
generatePath() {
|
|
20
|
+
try {
|
|
21
|
+
const slug = this.createSlug(this.input_textTarget.value);
|
|
22
|
+
// For posts, we want to store just the slug part
|
|
23
|
+
const prefix = this.output_textTarget.dataset.prefix || "";
|
|
24
|
+
this.output_textTarget.value = "/" + slug;
|
|
25
|
+
|
|
26
|
+
// If there's a prefix, show it in the UI but don't include it in the value
|
|
27
|
+
if (prefix) {
|
|
28
|
+
const prefixSpan = this.output_textTarget.previousElementSibling ||
|
|
29
|
+
(() => {
|
|
30
|
+
const span = document.createElement('span');
|
|
31
|
+
span.className = 'prefix';
|
|
32
|
+
this.output_textTarget.parentNode.insertBefore(span, this.output_textTarget);
|
|
33
|
+
return span;
|
|
34
|
+
})();
|
|
35
|
+
prefixSpan.textContent = prefix;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log("Have set the path to: " + this.output_textTarget.value);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Error generating path:", error);
|
|
41
|
+
// Add error class to path field
|
|
42
|
+
this.output_textTarget.classList.add("error");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setPrePath() {
|
|
47
|
+
try {
|
|
48
|
+
const match = this.input_selectTarget.options[this.input_selectTarget.selectedIndex].text.match(/.*\((.*)\)$/);
|
|
49
|
+
if (match) {
|
|
50
|
+
this.parent_slugs = match[1];
|
|
51
|
+
const prePath = (this.existing_rootTarget.value + this.parent_slugs).replace(/\/$/, "");
|
|
52
|
+
const prefixSpan = this.output_textTarget.previousElementSibling;
|
|
53
|
+
if (prefixSpan) {
|
|
54
|
+
prefixSpan.textContent = prePath;
|
|
55
|
+
}
|
|
56
|
+
console.log("Have set the pre-path to: " + prePath);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("Error setting pre-path:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// TODO: Invoke a library or helper which can be shared with the backend
|
|
64
|
+
// and check for uniqueness at the same time
|
|
65
|
+
createSlug(input) {
|
|
66
|
+
if (!input) return "";
|
|
67
|
+
|
|
68
|
+
var str = input
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.trim()
|
|
71
|
+
.replace(/[^\w\s-]/g, "-")
|
|
72
|
+
.replace(/&/g, "and")
|
|
73
|
+
.replace(/[\s_-]+/g, "-")
|
|
74
|
+
.trim();
|
|
75
|
+
|
|
76
|
+
return this.trimStartEnd(str, "-");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
trimStartEnd(str, ch) {
|
|
80
|
+
var start = 0;
|
|
81
|
+
var end = str.length;
|
|
82
|
+
|
|
83
|
+
while (start < end && str[start] === ch) ++start;
|
|
84
|
+
while (end > start && str[end - 1] === ch) --end;
|
|
85
|
+
return start > 0 || end < str.length ? str.substring(start, end) : str;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export class CSSExtractor {
|
|
2
|
+
/**
|
|
3
|
+
* Extracts CSS rules from within a specific selector and transforms them for EditorJS
|
|
4
|
+
* @param {string} css - The CSS content to parse
|
|
5
|
+
* @returns {string} The extracted and transformed CSS rules
|
|
6
|
+
*/
|
|
7
|
+
static extractStyles(css) {
|
|
8
|
+
const rules = []
|
|
9
|
+
let inComponents = false
|
|
10
|
+
let inContentRule = false
|
|
11
|
+
let braceCount = 0
|
|
12
|
+
let currentRule = ''
|
|
13
|
+
|
|
14
|
+
// Split CSS into lines and process each line
|
|
15
|
+
const lines = css.split('\n')
|
|
16
|
+
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
const trimmedLine = line.trim()
|
|
19
|
+
|
|
20
|
+
// Check if we're entering components layer
|
|
21
|
+
if (trimmedLine === '@layer components {') {
|
|
22
|
+
inComponents = true
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Only process lines within components layer
|
|
27
|
+
if (!inComponents) continue
|
|
28
|
+
|
|
29
|
+
// If we find the .content selector
|
|
30
|
+
if (!inContentRule && trimmedLine.startsWith('.content')) {
|
|
31
|
+
inContentRule = true
|
|
32
|
+
braceCount++
|
|
33
|
+
// Transform the selector for EditorJS
|
|
34
|
+
currentRule = '.codex-editor__redactor .ce-block .ce-block__content'
|
|
35
|
+
if (trimmedLine.includes('{')) {
|
|
36
|
+
currentRule += ' {'
|
|
37
|
+
}
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If we're inside a content rule
|
|
42
|
+
if (inContentRule) {
|
|
43
|
+
// Transform selectors for EditorJS
|
|
44
|
+
let transformedLine = line
|
|
45
|
+
.replace(/\.content\s+/g, '.codex-editor__redactor .ce-block .ce-block__content ')
|
|
46
|
+
.replace(/\bh1\b(?![-_])/g, 'h1.ce-header')
|
|
47
|
+
.replace(/\bh2\b(?![-_])/g, 'h2.ce-header')
|
|
48
|
+
.replace(/\bh3\b(?![-_])/g, 'h3.ce-header')
|
|
49
|
+
.replace(/\bul\b(?![-_])/g, 'ul.cdx-list')
|
|
50
|
+
.replace(/\bol\b(?![-_])/g, 'ol.cdx-list')
|
|
51
|
+
.replace(/\bli\b(?![-_])/g, 'li.cdx-list__item')
|
|
52
|
+
.replace(/\bblockquote\b(?![-_])/g, '.cdx-quote')
|
|
53
|
+
|
|
54
|
+
currentRule += '\n' + transformedLine
|
|
55
|
+
|
|
56
|
+
// Count braces to handle nested rules
|
|
57
|
+
braceCount += (trimmedLine.match(/{/g) || []).length
|
|
58
|
+
braceCount -= (trimmedLine.match(/}/g) || []).length
|
|
59
|
+
|
|
60
|
+
// If braces are balanced, we've found the end of the rule
|
|
61
|
+
if (braceCount === 0) {
|
|
62
|
+
rules.push(currentRule)
|
|
63
|
+
inContentRule = false
|
|
64
|
+
currentRule = ''
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return rules.join('\n\n')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets all styles from a stylesheet that apply to the editor
|
|
74
|
+
* @param {string} css - The CSS content to parse
|
|
75
|
+
* @returns {string} The extracted CSS rules
|
|
76
|
+
*/
|
|
77
|
+
static getEditorStyles(css) {
|
|
78
|
+
return this.extractStyles(css)
|
|
79
|
+
}
|
|
80
|
+
}
|