Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ SPDX-FileCopyrightText = "2020 Refactoring UI Inc"
SPDX-License-Identifier = "MIT"

[[annotations]]
path = "priv/vendor/jsoneditor*"
SPDX-FileCopyrightText = "2011-2021 Jos de Jong"
SPDX-License-Identifier = "Apache-2.0"
path = "priv/vendor/codemirror*"
SPDX-FileCopyrightText = "2017-2024 Marijn Haverbeke and others"
SPDX-License-Identifier = "MIT"

[[annotations]]
path = "priv/vendor/easymde*"
SPDX-FileCopyrightText = ["2015 Sparksuite, Inc", "2017 Jeroen Akkerman"]
path = ["priv/vendor/javascript.min.js", "priv/vendor/markdown.min.js"]
SPDX-FileCopyrightText = "2017-2024 Marijn Haverbeke and others"
SPDX-License-Identifier = "MIT"
159 changes: 92 additions & 67 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,98 +16,123 @@ const editors = {};
Hooks.JsonEditor = {
mounted() {
const inputId = this.el.getAttribute("data-input-id");
const hook = this;
this.editor = new JSONEditor(
this.el,
{
onChangeText: (json) => {
const target = document.getElementById(inputId);
try {
JSON.parse(json);
target.value = json;
target.dispatchEvent(
new Event("change", { bubbles: true, target: this.el.name }),
);
} catch (_e) { }
},
onChange: () => {
try {
const target = document.getElementById(inputId);
json = hook.editor.get();

target.value = JSON.stringify(json);
target.dispatchEvent(
new Event("change", { bubbles: true, target: this.el.name }),
);
} catch (_e) { }
},
onModeChange: (newMode) => {
hook.mode = newMode;
},
modes: ["text", "tree"],
},
JSON.parse(document.getElementById(inputId).value),
);
const target = document.getElementById(inputId);

const textarea = document.createElement("textarea");
textarea.value = target.value || "{}";
this.el.appendChild(textarea);

this.editor = CodeMirror.fromTextArea(textarea, {
mode: "javascript",
lineNumbers: true,
theme: "default",
lineWrapping: true,
height: "400px"
});

try {
const parsed = JSON.parse(target.value || "{}");
this.editor.setValue(JSON.stringify(parsed, null, 2));
} catch (e) {
this.editor.setValue(target.value || "{}");
}

this.editor.on("change", () => {
target.value = this.editor.getValue();
target.dispatchEvent(new Event("change", { bubbles: true }));
});

editors[this.el.id] = this.editor;
},
destroyed() {
if (this.editor) {
this.editor.toTextArea();
}
}
};

Hooks.JsonEditorSource = {
updated() {
try {
let editor = editors[this.el.getAttribute("data-editor-id")];
if (editor.getMode() === "tree") {
editor.update(JSON.parse(this.el.value));
} else {
if (editor.get() !== JSON.parse(this.el.value)) {
editor.setText(this.el.value);
} else {
const editor = editors[this.el.getAttribute("data-editor-id")];
if (editor && this.el.value) {
const currentValue = editor.getValue();
if (currentValue !== this.el.value) {
try {
const parsed = JSON.parse(this.el.value);
editor.setValue(JSON.stringify(parsed, null, 2));
} catch (_e) {
editor.setValue(this.el.value);
}
}
}
} catch (_e) { }
},
}
};

Hooks.JsonView = {
updated() {
const json = JSON.parse(this.el.getAttribute("data-json"));
this.editor = new JSONEditor(
this.el,
{
mode: "preview",
},
json,
);
},
mounted() {
const json = JSON.parse(this.el.getAttribute("data-json"));
this.editor = new JSONEditor(
this.el,
{
mode: "preview",
},
json,
);
const jsonStr = this.el.getAttribute("data-json");
const textarea = document.createElement("textarea");

try {
const json = JSON.parse(jsonStr);
textarea.value = JSON.stringify(json, null, 2);
} catch (e) {
textarea.value = jsonStr;
}

this.el.appendChild(textarea);

this.editor = CodeMirror.fromTextArea(textarea, {
mode: "javascript",
lineNumbers: true,
readOnly: true,
theme: "default",
lineWrapping: true
});
},
updated() {
if (this.editor) {
const jsonStr = this.el.getAttribute("data-json");
try {
const json = JSON.parse(jsonStr);
this.editor.setValue(JSON.stringify(json, null, 2));
} catch (e) {
this.editor.setValue(jsonStr);
}
}
},
destroyed() {
if (this.editor) {
this.editor.toTextArea();
}
}
};

const init = (element) =>
new EasyMDE({
element: element,
initialValue: element.getAttribute("value"),
});

Hooks.MarkdownEditor = {
mounted() {
const id = this.el.getAttribute("data-target-id");
const el = document.getElementById(id);
const easyMDE = init(el);
easyMDE.codemirror.on("change", () => {
el.value = easyMDE.value();

this.editor = CodeMirror.fromTextArea(el, {
mode: "markdown",
lineNumbers: true,
theme: "default",
lineWrapping: true,
height: "300px"
});

this.editor.on("change", () => {
el.value = this.editor.getValue();
el.dispatchEvent(new Event("change", { bubbles: true }));
});
},
destroyed() {
if (this.editor) {
this.editor.toTextArea();
}
}
};

Hooks.Actor = {
Expand Down
38 changes: 18 additions & 20 deletions lib/ash_admin/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ defmodule AshAdmin.Layouts do
@external_resource css_path = Path.join(@static_path, "assets/app.css")

# Vendor files
@external_resource easymde_js_path = Path.join(@vendor_path, "easymde.min.js")
@external_resource easymde_css_path = Path.join(@vendor_path, "easymde.min.css")
@external_resource jsoneditor_js_path = Path.join(@vendor_path, "jsoneditor.min.js")
@external_resource jsoneditor_css_path = Path.join(@vendor_path, "jsoneditor.min.css")
@external_resource codemirror_js_path = Path.join(@vendor_path, "codemirror.min.js")
@external_resource codemirror_css_path = Path.join(@vendor_path, "codemirror.min.css")
@external_resource codemirror_js_mode_path = Path.join(@vendor_path, "javascript.min.js")
@external_resource codemirror_md_mode_path = Path.join(@vendor_path, "markdown.min.js")

@app_js """
#{for path <- phoenix_js_paths, do: path |> File.read!() |> String.replace("//# sourceMappingURL=", "// ")}
Expand All @@ -34,19 +34,17 @@ defmodule AshAdmin.Layouts do
@app_css File.read!(css_path)

# Read vendor files at compile time
@easymde_js if File.exists?(easymde_js_path), do: File.read!(easymde_js_path), else: ""
@easymde_css if File.exists?(easymde_css_path), do: File.read!(easymde_css_path), else: ""
@jsoneditor_js if File.exists?(jsoneditor_js_path), do: File.read!(jsoneditor_js_path), else: ""
@jsoneditor_css if File.exists?(jsoneditor_css_path),
do: File.read!(jsoneditor_css_path),
else: ""
@codemirror_js if File.exists?(codemirror_js_path), do: File.read!(codemirror_js_path), else: ""
@codemirror_css if File.exists?(codemirror_css_path), do: File.read!(codemirror_css_path), else: ""
@codemirror_js_mode if File.exists?(codemirror_js_mode_path), do: File.read!(codemirror_js_mode_path), else: ""
@codemirror_md_mode if File.exists?(codemirror_md_mode_path), do: File.read!(codemirror_md_mode_path), else: ""

def render("app.js", _), do: @app_js
def render("app.css", _), do: @app_css
def render("easymde.js", _), do: @easymde_js
def render("easymde.css", _), do: @easymde_css
def render("jsoneditor.js", _), do: @jsoneditor_js
def render("jsoneditor.css", _), do: @jsoneditor_css
def render("codemirror.js", _), do: @codemirror_js
def render("codemirror.css", _), do: @codemirror_css
def render("codemirror-js-mode.js", _), do: @codemirror_js_mode
def render("codemirror-md-mode.js", _), do: @codemirror_md_mode

def render("root.html", assigns) do
~H"""
Expand All @@ -62,16 +60,16 @@ defmodule AshAdmin.Layouts do
<%= raw(render("app.css", %{})) %>
</style>
<style nonce={csp_nonce(@conn, :style)}>
<%= raw(render("jsoneditor.css", %{})) %>
</style>
<style nonce={csp_nonce(@conn, :style)}>
<%= raw(render("easymde.css", %{})) %>
<%= raw(render("codemirror.css", %{})) %>
</style>
<script nonce={csp_nonce(@conn, :script)}>
<%= raw(render("jsoneditor.js", %{})) %>
<%= raw(render("codemirror.js", %{})) %>
</script>
<script nonce={csp_nonce(@conn, :script)}>
<%= raw(render("codemirror-js-mode.js", %{})) %>
</script>
<script nonce={csp_nonce(@conn, :script)}>
<%= raw(render("easymde.js", %{})) %>
<%= raw(render("codemirror-md-mode.js", %{})) %>
</script>
</head>
<body>
Expand Down
7 changes: 6 additions & 1 deletion lib/ash_admin/components/resource/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -776,10 +776,15 @@ defmodule AshAdmin.Components.Resource.Form do
/>
<%= if length(@upload.entries) > 0 do %>
<div class="w-full bg-gray-200 rounded-full h-1.5 mb-1 mt-1">
<style nonce={Process.get(:ash_admin_csp_nonce_style)}>
#progress {
width: <%= hd(@upload.entries).progress %>%;
}
</style>
<div
id="progress"
class="bg-indigo-600 h-1.5 rounded-full"
data-progress={hd(@upload.entries).progress}
style={"width: #{hd(@upload.entries).progress}%"}
>
</div>
</div>
Expand Down
8 changes: 8 additions & 0 deletions lib/ash_admin/pages/page_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ defmodule AshAdmin.PageLive do
} = session,
socket
) do
csp_nonce_assign_key = get_in(socket.private, [:ash_admin_csp_nonce, :style])
if csp_nonce_assign_key do
csp_nonce_value = Map.get(socket.assigns, csp_nonce_assign_key)
if csp_nonce_value do
Process.put(:ash_admin_csp_nonce_style, csp_nonce_value)
end
end

otp_app = socket.endpoint.config(:otp_app)

prefix =
Expand Down
Loading