Compare commits
2
Commits
b016f6d812
...
8fe18d108f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe18d108f | ||
|
|
b517672663 |
No files matched your search
@@ -34,6 +34,20 @@ export const webKitTextAreaArrowCommand = (event) => {
|
|||||||
return event.shiftKey ? `${command}Select` : command;
|
return event.shiftKey ? `${command}Select` : command;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Applying a remote value via setValue resets the cursor to the buffer start,
|
||||||
|
// which then makes follow-up actions (like link inserts targeting the current
|
||||||
|
// selection) land at position 1,1. Capture and restore the selection so the
|
||||||
|
// cursor survives server-driven reconciles; Monaco clamps out-of-range
|
||||||
|
// positions to the new content.
|
||||||
|
export const applyRemoteEditorValue = (editor, value) => {
|
||||||
|
const selections = editor.getSelections ? editor.getSelections() : null;
|
||||||
|
editor.setValue(value);
|
||||||
|
|
||||||
|
if (selections && selections.length > 0 && editor.setSelections) {
|
||||||
|
editor.setSelections(selections);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const bridgeWebKitTextAreaArrowKey = (editor, event) => {
|
export const bridgeWebKitTextAreaArrowKey = (editor, event) => {
|
||||||
if (!editor || !isMonacoInputArea(event.target)) {
|
if (!editor || !isMonacoInputArea(event.target)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -92,8 +106,12 @@ export const MonacoEditor = {
|
|||||||
|
|
||||||
if (this.editor.getValue() !== value) {
|
if (this.editor.getValue() !== value) {
|
||||||
this.isApplyingRemoteUpdate = true;
|
this.isApplyingRemoteUpdate = true;
|
||||||
this.editor.setValue(value);
|
|
||||||
this.isApplyingRemoteUpdate = false;
|
try {
|
||||||
|
applyRemoteEditorValue(this.editor, value);
|
||||||
|
} finally {
|
||||||
|
this.isApplyingRemoteUpdate = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastKnownValue = value;
|
this.lastKnownValue = value;
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
ShellOverlayComponents.markdown_link(result.title, result.canonical_url)}
|
ShellOverlayComponents.markdown_link(result.title, result.canonical_url)}
|
||||||
)
|
)
|
||||||
|
|
||||||
socket
|
assign(socket, :shell_overlay, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
{%{kind: :insert_media}, %{type: :post, id: post_id}} ->
|
{%{kind: :insert_media}, %{type: :post, id: post_id}} ->
|
||||||
@@ -172,7 +172,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
end
|
end
|
||||||
|
|
||||||
Notify.parent({:post_editor_insert_content, post_id, syntax})
|
Notify.parent({:post_editor_insert_content, post_id, syntax})
|
||||||
socket
|
assign(socket, :shell_overlay, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
_other ->
|
_other ->
|
||||||
@@ -197,10 +197,11 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
|
|
||||||
if details do
|
if details do
|
||||||
Notify.parent({:post_editor_insert_content, post_id, details})
|
Notify.parent({:post_editor_insert_content, post_id, details})
|
||||||
|
assign(socket, :shell_overlay, nil)
|
||||||
|
else
|
||||||
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
socket
|
|
||||||
|
|
||||||
_other ->
|
_other ->
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
id: socket.assigns.post_id,
|
id: socket.assigns.post_id,
|
||||||
content: content
|
content: content
|
||||||
})
|
})
|
||||||
|> assign(:shell_overlay, nil)
|
|
||||||
|
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
end
|
end
|
||||||
@@ -316,18 +315,6 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
{:noreply, do_remove_list_value(socket, :categories, category)}
|
{:noreply, do_remove_list_value(socket, :categories, category)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("insert_content", %{"content" => content}, socket) do
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> Phoenix.LiveView.push_event("post-editor-insert-content", %{
|
|
||||||
id: socket.assigns.post_id,
|
|
||||||
content: content
|
|
||||||
})
|
|
||||||
|> assign(:shell_overlay, nil)
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("close_quick_actions", _params, socket) do
|
def handle_event("close_quick_actions", _params, socket) do
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
@@ -892,7 +879,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if map_size(attrs) == 0 do
|
if map_size(attrs) == 0 do
|
||||||
assign(socket, :shell_overlay, nil)
|
socket
|
||||||
else
|
else
|
||||||
case Posts.update_post(post_id, attrs) do
|
case Posts.update_post(post_id, attrs) do
|
||||||
{:ok, updated_post} ->
|
{:ok, updated_post} ->
|
||||||
@@ -910,7 +897,6 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
)
|
)
|
||||||
|> assign(:save_state, :dirty)
|
|> assign(:save_state, :dirty)
|
||||||
|> assign(:dirty?, true)
|
|> assign(:dirty?, true)
|
||||||
|> assign(:shell_overlay, nil)
|
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
Notify.dirty(:post, post_id, true)
|
Notify.dirty(:post, post_id, true)
|
||||||
|
|||||||
+50
-3
@@ -130,8 +130,10 @@ defmodule BDS.MacBundle do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Fail the build unless the bundle is self-contained: no Mach-O under the `.app`
|
Fail the build unless the bundle is self-contained: no Mach-O under the `.app`
|
||||||
may reference a Homebrew/local (`/opt/homebrew`, `/usr/local`) path. This is a
|
may reference a Homebrew/local (`/opt/homebrew`, `/usr/local`) path, and every
|
||||||
hard requirement — the app must run on a Mac without Homebrew installed.
|
`@rpath/` dependency must resolve — via the binary's own `LC_RPATH`s — to a
|
||||||
|
file inside the bundle. This is a hard requirement — the app must run on a
|
||||||
|
Mac without Homebrew installed.
|
||||||
"""
|
"""
|
||||||
@spec verify_standalone(String.t()) ::
|
@spec verify_standalone(String.t()) ::
|
||||||
:ok | {:error, {:external_refs, [{String.t(), String.t()}]}}
|
:ok | {:error, {:external_refs, [{String.t(), String.t()}]}}
|
||||||
@@ -141,7 +143,8 @@ defmodule BDS.MacBundle do
|
|||||||
|> macho_files()
|
|> macho_files()
|
||||||
|> Enum.flat_map(fn file ->
|
|> Enum.flat_map(fn file ->
|
||||||
external_refs(file, ["-L"], &Dylibs.parse_otool/1) ++
|
external_refs(file, ["-L"], &Dylibs.parse_otool/1) ++
|
||||||
external_refs(file, ["-l"], &Dylibs.parse_rpaths/1)
|
external_refs(file, ["-l"], &Dylibs.parse_rpaths/1) ++
|
||||||
|
unresolved_rpath_refs(file, app)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if offenders == [], do: :ok, else: {:error, {:external_refs, offenders}}
|
if offenders == [], do: :ok, else: {:error, {:external_refs, offenders}}
|
||||||
@@ -154,6 +157,50 @@ defmodule BDS.MacBundle do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# An @rpath/ dep is an offender unless one of its rpath-resolved candidates
|
||||||
|
# exists inside the app — a dangling one (Homebrew's libwebp referencing
|
||||||
|
# @rpath/libsharpyuv.0.dylib with no copied sibling) crashes only at runtime
|
||||||
|
# on the user's machine, so it must fail the build here. A dylib's own
|
||||||
|
# LC_ID_DYLIB shows up in `otool -L` like a dep (precompiled NIFs such as
|
||||||
|
# hnswlib_nif.so ship an @rpath/ self id with no rpaths at all); dyld never
|
||||||
|
# resolves a file's id when loading that file, so self references are skipped.
|
||||||
|
defp unresolved_rpath_refs(file, app) do
|
||||||
|
root = Path.expand(app)
|
||||||
|
|
||||||
|
case otool_parse(file, ["-L"], &Dylibs.parse_otool/1) do
|
||||||
|
{:ok, deps} ->
|
||||||
|
deps
|
||||||
|
|> Enum.filter(&String.starts_with?(&1, "@rpath/"))
|
||||||
|
|> Enum.reject(&(Path.basename(&1) == Path.basename(file)))
|
||||||
|
|> Enum.reject(&rpath_dep_resolves?(file, &1, root))
|
||||||
|
|> Enum.map(&{file, &1})
|
||||||
|
|
||||||
|
_error ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rpath_dep_resolves?(file, dep, root) do
|
||||||
|
case otool_parse(file, ["-l"], &Dylibs.parse_rpaths/1) do
|
||||||
|
{:ok, rpaths} ->
|
||||||
|
dep
|
||||||
|
|> Dylibs.resolve_rpath_dep(rpaths, Path.dirname(file))
|
||||||
|
|> Enum.any?(fn candidate ->
|
||||||
|
String.starts_with?(candidate, root <> "/") and File.exists?(candidate)
|
||||||
|
end)
|
||||||
|
|
||||||
|
_error ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp otool_parse(file, args, parse) do
|
||||||
|
case System.cmd("otool", args ++ [file], stderr_to_stdout: true) do
|
||||||
|
{out, 0} -> {:ok, parse.(out)}
|
||||||
|
{out, status} -> {:error, {:otool_failed, status, out}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Compute a `../`-style relative path from `from_dir` to `to` (both absolute),
|
Compute a `../`-style relative path from `from_dir` to `to` (both absolute),
|
||||||
e.g. the path from the wx NIF's directory up to `Contents/Frameworks`.
|
e.g. the path from the wx NIF's directory up to `Contents/Frameworks`.
|
||||||
|
|||||||
@@ -103,16 +103,43 @@ defmodule BDS.MacBundle.Dylibs do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Build `{old, new}` rewrites for a binary's `otool -L` dependency list: every
|
Resolve an `@rpath/<name>` dependency to absolute candidate paths using the
|
||||||
external dep becomes `<prefix>/<basename>`. System (`/usr/lib`, `/System`) and
|
referencing binary's `LC_RPATH` list, the way dyld would: each rpath entry is
|
||||||
already-relocatable (`@rpath`/`@loader_path`/`@executable_path`) deps are
|
tried in order with `@loader_path` expanded to the binary's own directory.
|
||||||
dropped. Rewriting by basename — not by the exact source path — is what makes
|
`@executable_path` rpaths are skipped (not resolvable from a library), and
|
||||||
this catch every absolute spelling of the same library.
|
non-`@rpath` deps yield no candidates. Homebrew links keg siblings this way
|
||||||
|
(`@rpath/libsharpyuv.0.dylib` + rpath `@loader_path/../lib` in libwebp), so
|
||||||
|
these deps are just as external as absolute `/opt/homebrew` ones.
|
||||||
"""
|
"""
|
||||||
@spec relink_changes([String.t()], String.t()) :: [{String.t(), String.t()}]
|
@spec resolve_rpath_dep(String.t(), [String.t()], String.t()) :: [String.t()]
|
||||||
def relink_changes(deps, prefix) when is_list(deps) do
|
def resolve_rpath_dep("@rpath/" <> name, rpaths, loader_dir) when is_list(rpaths) do
|
||||||
|
rpaths
|
||||||
|
|> Enum.map(fn
|
||||||
|
"@loader_path" <> rest -> Path.expand(loader_dir <> rest)
|
||||||
|
"@executable_path" <> _rest -> nil
|
||||||
|
absolute -> absolute
|
||||||
|
end)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|> Enum.map(&Path.join(&1, name))
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_rpath_dep(_dep, _rpaths, _loader_dir), do: []
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Build `{old, new}` rewrites for a binary's `otool -L` dependency list: every
|
||||||
|
external dep becomes `<prefix>/<basename>`, and every `@rpath/<name>` dep
|
||||||
|
whose basename is in `bundled` (it was copied into Frameworks) is rewritten
|
||||||
|
the same way. System (`/usr/lib`, `/System`) and other already-relocatable
|
||||||
|
deps are dropped. Rewriting by basename — not by the exact source path — is
|
||||||
|
what makes this catch every absolute spelling of the same library.
|
||||||
|
"""
|
||||||
|
@spec relink_changes([String.t()], String.t(), [String.t()]) :: [{String.t(), String.t()}]
|
||||||
|
def relink_changes(deps, prefix, bundled \\ []) when is_list(deps) do
|
||||||
deps
|
deps
|
||||||
|> Enum.filter(&external?/1)
|
|> Enum.filter(fn
|
||||||
|
"@rpath/" <> name -> name in bundled
|
||||||
|
dep -> external?(dep)
|
||||||
|
end)
|
||||||
|> Enum.map(fn dep -> {dep, prefix <> "/" <> Path.basename(dep)} end)
|
|> Enum.map(fn dep -> {dep, prefix <> "/" <> Path.basename(dep)} end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -135,9 +162,10 @@ defmodule BDS.MacBundle.Dylibs do
|
|||||||
|
|
||||||
with {:ok, externals} <- collect(nifs, %{}) do
|
with {:ok, externals} <- collect(nifs, %{}) do
|
||||||
reals = materialize(externals, frameworks_dir)
|
reals = materialize(externals, frameworks_dir)
|
||||||
|
bundled = Map.keys(externals)
|
||||||
|
|
||||||
with :ok <- relink_each(reals, fn _real -> "@loader_path" end, set_id: true),
|
with :ok <- relink_each(reals, fn _real -> "@loader_path" end, bundled, set_id: true),
|
||||||
:ok <- relink_each(nifs, prefix_for, set_id: false) do
|
:ok <- relink_each(nifs, prefix_for, bundled, set_id: false) do
|
||||||
{:ok, reals}
|
{:ok, reals}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -147,10 +175,9 @@ defmodule BDS.MacBundle.Dylibs do
|
|||||||
# basename — different absolute spellings of the same leaf collapse here).
|
# basename — different absolute spellings of the same leaf collapse here).
|
||||||
defp collect(binaries, acc) do
|
defp collect(binaries, acc) do
|
||||||
Enum.reduce_while(binaries, {:ok, acc}, fn bin, {:ok, acc} ->
|
Enum.reduce_while(binaries, {:ok, acc}, fn bin, {:ok, acc} ->
|
||||||
case otool(bin) do
|
case external_deps(bin) do
|
||||||
{:ok, deps} ->
|
{:ok, deps} ->
|
||||||
deps
|
deps
|
||||||
|> Enum.filter(&external?/1)
|
|
||||||
|> Enum.reduce_while({:ok, acc}, fn dep, {:ok, acc} ->
|
|> Enum.reduce_while({:ok, acc}, fn dep, {:ok, acc} ->
|
||||||
base = Path.basename(dep)
|
base = Path.basename(dep)
|
||||||
|
|
||||||
@@ -174,6 +201,42 @@ defmodule BDS.MacBundle.Dylibs do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A binary's external deps: absolute Homebrew/local references, plus any
|
||||||
|
# @rpath/ reference that resolves (via the binary's own LC_RPATHs) to an
|
||||||
|
# existing Homebrew/local file. @rpath deps resolving inside the release tree
|
||||||
|
# (vix/exla-style precompiled NIFs) are relocatable as-is and stay untouched.
|
||||||
|
defp external_deps(bin) do
|
||||||
|
with {:ok, deps} <- otool(bin) do
|
||||||
|
direct = Enum.filter(deps, &external?/1)
|
||||||
|
|
||||||
|
case resolve_external_rpath_deps(bin, deps) do
|
||||||
|
{:ok, resolved} -> {:ok, direct ++ resolved}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resolve_external_rpath_deps(bin, deps) do
|
||||||
|
case Enum.filter(deps, &String.starts_with?(&1, "@rpath/")) do
|
||||||
|
[] ->
|
||||||
|
{:ok, []}
|
||||||
|
|
||||||
|
rpath_deps ->
|
||||||
|
with {:ok, search_paths} <- rpaths(bin) do
|
||||||
|
resolved =
|
||||||
|
rpath_deps
|
||||||
|
|> Enum.map(fn dep ->
|
||||||
|
dep
|
||||||
|
|> resolve_rpath_dep(search_paths, Path.dirname(bin))
|
||||||
|
|> Enum.find(fn candidate -> external?(candidate) and File.exists?(candidate) end)
|
||||||
|
end)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
|
{:ok, resolved}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Copy each external once (deduped by device+inode, which follows symlinks to
|
# Copy each external once (deduped by device+inode, which follows symlinks to
|
||||||
# the real file); additional basenames for the same file become symlinks so
|
# the real file); additional basenames for the same file become symlinks so
|
||||||
# dyld loads a single image. Returns the real (non-symlink) copies.
|
# dyld loads a single image. Returns the real (non-symlink) copies.
|
||||||
@@ -206,12 +269,12 @@ defmodule BDS.MacBundle.Dylibs do
|
|||||||
{stat.major_device, stat.inode}
|
{stat.major_device, stat.inode}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp relink_each(binaries, prefix_for, opts) do
|
defp relink_each(binaries, prefix_for, bundled, opts) do
|
||||||
set_id? = Keyword.fetch!(opts, :set_id)
|
set_id? = Keyword.fetch!(opts, :set_id)
|
||||||
|
|
||||||
Enum.reduce_while(binaries, :ok, fn binary, :ok ->
|
Enum.reduce_while(binaries, :ok, fn binary, :ok ->
|
||||||
with :ok <- maybe_set_id(binary, set_id?),
|
with :ok <- maybe_set_id(binary, set_id?),
|
||||||
:ok <- relink(binary, prefix_for.(binary)) do
|
:ok <- relink(binary, prefix_for.(binary), bundled) do
|
||||||
{:cont, :ok}
|
{:cont, :ok}
|
||||||
else
|
else
|
||||||
error -> {:halt, error}
|
error -> {:halt, error}
|
||||||
@@ -225,9 +288,9 @@ defmodule BDS.MacBundle.Dylibs do
|
|||||||
run("install_name_tool", id_args(binary, "@loader_path/" <> Path.basename(binary)))
|
run("install_name_tool", id_args(binary, "@loader_path/" <> Path.basename(binary)))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp relink(binary, prefix) do
|
defp relink(binary, prefix, bundled) do
|
||||||
with {:ok, deps} <- otool(binary),
|
with {:ok, deps} <- otool(binary),
|
||||||
:ok <- apply_changes(binary, change_args(binary, relink_changes(deps, prefix))) do
|
:ok <- apply_changes(binary, change_args(binary, relink_changes(deps, prefix, bundled))) do
|
||||||
strip_external_rpaths(binary)
|
strip_external_rpaths(binary)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Vendored
+2
-2
@@ -21,8 +21,8 @@ removing illegal node: "${(s.outerHTML||s.nodeValue).trim()}"
|
|||||||
let liveSocket = new LiveSocket("/live", Socket, {...})
|
let liveSocket = new LiveSocket("/live", Socket, {...})
|
||||||
`);this.socket=new t(e,i),this.bindingPrefix=i.bindingPrefix||jn,this.opts=i,this.params=ze(i.params||{}),this.viewLogger=i.viewLogger,this.metadataCallbacks=i.metadata||{},this.defaults=Object.assign(bt(Vn),i.defaults||{}),this.prevActive=null,this.silenced=!1,this.main=null,this.outgoingMainEl=null,this.clickStartedAtTarget=null,this.linkRef=1,this.roots={},this.href=window.location.href,this.pendingLink=null,this.currentLocation=bt(window.location),this.hooks=i.hooks||{},this.uploaders=i.uploaders||{},this.loaderTimeout=i.loaderTimeout||$n,this.disconnectedTimeout=i.disconnectedTimeout||Un,this.reloadWithJitterTimer=null,this.maxReloads=i.maxReloads||An,this.reloadJitterMin=i.reloadJitterMin||kn,this.reloadJitterMax=i.reloadJitterMax||Tn,this.failsafeJitter=i.failsafeJitter||Cn,this.localStorage=i.localStorage||window.localStorage,this.sessionStorage=i.sessionStorage||window.sessionStorage,this.boundTopLevelEvents=!1,this.boundEventNames=new Set,this.blockPhxChangeWhileComposing=i.blockPhxChangeWhileComposing||!1,this.serverCloseRef=null,this.domCallbacks=Object.assign({jsQuerySelectorAll:null,onPatchStart:ze(),onPatchEnd:ze(),onNodeAdded:ze(),onBeforeElUpdated:ze()},i.dom||{}),this.transitions=new kr,this.currentHistoryPosition=parseInt(this.sessionStorage.getItem(ct))||0,window.addEventListener("pagehide",s=>{this.unloaded=!0}),this.socket.onOpen(()=>{this.isUnloaded()&&window.location.reload()})}version(){return"1.1.28"}isProfileEnabled(){return this.sessionStorage.getItem(Ft)==="true"}isDebugEnabled(){return this.sessionStorage.getItem(ht)==="true"}isDebugDisabled(){return this.sessionStorage.getItem(ht)==="false"}enableDebug(){this.sessionStorage.setItem(ht,"true")}enableProfiling(){this.sessionStorage.setItem(Ft,"true")}disableDebug(){this.sessionStorage.setItem(ht,"false")}disableProfiling(){this.sessionStorage.removeItem(Ft)}enableLatencySim(e){this.enableDebug(),console.log("latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable"),this.sessionStorage.setItem(Ut,e)}disableLatencySim(){this.sessionStorage.removeItem(Ut)}getLatencySim(){let e=this.sessionStorage.getItem(Ut);return e?parseInt(e):null}getSocket(){return this.socket}connect(){window.location.hostname==="localhost"&&!this.isDebugDisabled()&&this.enableDebug();let e=()=>{this.resetReloadStatus(),this.joinRootViews()?(this.bindTopLevelEvents(),this.socket.connect()):this.main?this.socket.connect():this.bindTopLevelEvents({dead:!0}),this.joinDeadView()};["complete","loaded","interactive"].indexOf(document.readyState)>=0?e():document.addEventListener("DOMContentLoaded",()=>e())}disconnect(e){clearTimeout(this.reloadWithJitterTimer),this.serverCloseRef&&(this.socket.off(this.serverCloseRef),this.serverCloseRef=null),this.socket.disconnect(e)}replaceTransport(e){clearTimeout(this.reloadWithJitterTimer),this.socket.replaceTransport(e),this.connect()}execJS(e,t,i=null){let s=new CustomEvent("phx:exec",{detail:{sourceElement:e}});this.owner(e,n=>P.exec(s,i,t,n,e))}js(){return hs(this,"js")}unload(){this.unloaded||(this.main&&this.isConnected()&&this.log(this.main,"socket",()=>["disconnect for page nav"]),this.unloaded=!0,this.destroyAllViews(),this.disconnect())}triggerDOM(e,t){this.domCallbacks[e](...t)}time(e,t){if(!this.isProfileEnabled()||!console.time)return t();console.time(e);let i=t();return console.timeEnd(e),i}log(e,t,i){if(this.viewLogger){let[s,n]=i();this.viewLogger(e,t,s,n)}else if(this.isDebugEnabled()){let[s,n]=i();Jn(e,t,s,n)}}requestDOMUpdate(e){this.transitions.after(e)}asyncTransition(e){this.transitions.addAsyncTransition(e)}transition(e,t,i=function(){}){this.transitions.addTransition(e,t,i)}onChannel(e,t,i){e.on(t,s=>{let n=this.getLatencySim();n?setTimeout(()=>i(s),n):i(s)})}reloadWithJitter(e,t){clearTimeout(this.reloadWithJitterTimer),this.disconnect();let i=this.reloadJitterMin,s=this.reloadJitterMax,n=Math.floor(Math.random()*(s-i+1))+i,r=B.updateLocal(this.localStorage,window.location.pathname,ts,0,o=>o+1);r>=this.maxReloads&&(n=this.failsafeJitter),this.reloadWithJitterTimer=setTimeout(()=>{e.isDestroyed()||e.isConnected()||(e.destroy(),t?t():this.log(e,"join",()=>[`encountered ${r} consecutive reloads`]),r>=this.maxReloads&&this.log(e,"join",()=>[`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]),this.hasPendingLink()?window.location=this.pendingLink:window.location.reload())},n)}getHookDefinition(e){if(e)return this.maybeInternalHook(e)||this.hooks[e]||this.maybeRuntimeHook(e)}maybeInternalHook(e){return e&&e.startsWith("Phoenix.")&&ir[e.split(".")[1]]}maybeRuntimeHook(e){let t=document.querySelector(`script[${vt}="${CSS.escape(e)}"]`);if(!t)return;let i=window[`phx_hook_${e}`];if(!i||typeof i!="function"){I("a runtime hook must be a function",t);return}let s=i();if(s&&(typeof s=="object"||typeof s=="function"))return s;I("runtime hook must return an object with hook callbacks or an instance of ViewHook",t)}isUnloaded(){return this.u
Line truncated
|
`);this.socket=new t(e,i),this.bindingPrefix=i.bindingPrefix||jn,this.opts=i,this.params=ze(i.params||{}),this.viewLogger=i.viewLogger,this.metadataCallbacks=i.metadata||{},this.defaults=Object.assign(bt(Vn),i.defaults||{}),this.prevActive=null,this.silenced=!1,this.main=null,this.outgoingMainEl=null,this.clickStartedAtTarget=null,this.linkRef=1,this.roots={},this.href=window.location.href,this.pendingLink=null,this.currentLocation=bt(window.location),this.hooks=i.hooks||{},this.uploaders=i.uploaders||{},this.loaderTimeout=i.loaderTimeout||$n,this.disconnectedTimeout=i.disconnectedTimeout||Un,this.reloadWithJitterTimer=null,this.maxReloads=i.maxReloads||An,this.reloadJitterMin=i.reloadJitterMin||kn,this.reloadJitterMax=i.reloadJitterMax||Tn,this.failsafeJitter=i.failsafeJitter||Cn,this.localStorage=i.localStorage||window.localStorage,this.sessionStorage=i.sessionStorage||window.sessionStorage,this.boundTopLevelEvents=!1,this.boundEventNames=new Set,this.blockPhxChangeWhileComposing=i.blockPhxChangeWhileComposing||!1,this.serverCloseRef=null,this.domCallbacks=Object.assign({jsQuerySelectorAll:null,onPatchStart:ze(),onPatchEnd:ze(),onNodeAdded:ze(),onBeforeElUpdated:ze()},i.dom||{}),this.transitions=new kr,this.currentHistoryPosition=parseInt(this.sessionStorage.getItem(ct))||0,window.addEventListener("pagehide",s=>{this.unloaded=!0}),this.socket.onOpen(()=>{this.isUnloaded()&&window.location.reload()})}version(){return"1.1.28"}isProfileEnabled(){return this.sessionStorage.getItem(Ft)==="true"}isDebugEnabled(){return this.sessionStorage.getItem(ht)==="true"}isDebugDisabled(){return this.sessionStorage.getItem(ht)==="false"}enableDebug(){this.sessionStorage.setItem(ht,"true")}enableProfiling(){this.sessionStorage.setItem(Ft,"true")}disableDebug(){this.sessionStorage.setItem(ht,"false")}disableProfiling(){this.sessionStorage.removeItem(Ft)}enableLatencySim(e){this.enableDebug(),console.log("latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable"),this.sessionStorage.setItem(Ut,e)}disableLatencySim(){this.sessionStorage.removeItem(Ut)}getLatencySim(){let e=this.sessionStorage.getItem(Ut);return e?parseInt(e):null}getSocket(){return this.socket}connect(){window.location.hostname==="localhost"&&!this.isDebugDisabled()&&this.enableDebug();let e=()=>{this.resetReloadStatus(),this.joinRootViews()?(this.bindTopLevelEvents(),this.socket.connect()):this.main?this.socket.connect():this.bindTopLevelEvents({dead:!0}),this.joinDeadView()};["complete","loaded","interactive"].indexOf(document.readyState)>=0?e():document.addEventListener("DOMContentLoaded",()=>e())}disconnect(e){clearTimeout(this.reloadWithJitterTimer),this.serverCloseRef&&(this.socket.off(this.serverCloseRef),this.serverCloseRef=null),this.socket.disconnect(e)}replaceTransport(e){clearTimeout(this.reloadWithJitterTimer),this.socket.replaceTransport(e),this.connect()}execJS(e,t,i=null){let s=new CustomEvent("phx:exec",{detail:{sourceElement:e}});this.owner(e,n=>P.exec(s,i,t,n,e))}js(){return hs(this,"js")}unload(){this.unloaded||(this.main&&this.isConnected()&&this.log(this.main,"socket",()=>["disconnect for page nav"]),this.unloaded=!0,this.destroyAllViews(),this.disconnect())}triggerDOM(e,t){this.domCallbacks[e](...t)}time(e,t){if(!this.isProfileEnabled()||!console.time)return t();console.time(e);let i=t();return console.timeEnd(e),i}log(e,t,i){if(this.viewLogger){let[s,n]=i();this.viewLogger(e,t,s,n)}else if(this.isDebugEnabled()){let[s,n]=i();Jn(e,t,s,n)}}requestDOMUpdate(e){this.transitions.after(e)}asyncTransition(e){this.transitions.addAsyncTransition(e)}transition(e,t,i=function(){}){this.transitions.addTransition(e,t,i)}onChannel(e,t,i){e.on(t,s=>{let n=this.getLatencySim();n?setTimeout(()=>i(s),n):i(s)})}reloadWithJitter(e,t){clearTimeout(this.reloadWithJitterTimer),this.disconnect();let i=this.reloadJitterMin,s=this.reloadJitterMax,n=Math.floor(Math.random()*(s-i+1))+i,r=B.updateLocal(this.localStorage,window.location.pathname,ts,0,o=>o+1);r>=this.maxReloads&&(n=this.failsafeJitter),this.reloadWithJitterTimer=setTimeout(()=>{e.isDestroyed()||e.isConnected()||(e.destroy(),t?t():this.log(e,"join",()=>[`encountered ${r} consecutive reloads`]),r>=this.maxReloads&&this.log(e,"join",()=>[`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]),this.hasPendingLink()?window.location=this.pendingLink:window.location.reload())},n)}getHookDefinition(e){if(e)return this.maybeInternalHook(e)||this.hooks[e]||this.maybeRuntimeHook(e)}maybeInternalHook(e){return e&&e.startsWith("Phoenix.")&&ir[e.split(".")[1]]}maybeRuntimeHook(e){let t=document.querySelector(`script[${vt}="${CSS.escape(e)}"]`);if(!t)return;let i=window[`phx_hook_${e}`];if(!i||typeof i!="function"){I("a runtime hook must be a function",t);return}let s=i();if(s&&(typeof s=="object"||typeof s=="function"))return s;I("runtime hook must return an object with hook callbacks or an instance of ViewHook",t)}isUnloaded(){return this.u
Line truncated
|
||||||
`)&&!this._prevInputValue.includes(`
|
`)&&!this._prevInputValue.includes(`
|
||||||
`)){t.value=t.value.replace(/\n/g,""),this._prevInputValue=t.value,this.stickToBottom=!0,this.autoResize(),this._submitChat();return}this._enterKeyHandled=!1,this._prevInputValue=t.value,this.stickToBottom=!0,this.autoResize()},this.handleKeyDown=e=>{e.target.closest(".chat-input")&&e.key==="Enter"&&!e.shiftKey&&!e.isComposing&&(e.preventDefault(),this._enterKeyHandled=!0,this._submitChat())},this.el.addEventListener("input",this.handleInput),this.el.addEventListener("keydown",this.handleKeyDown),this.syncScrollContainer(),this.syncExpandedSurfaces(),this.surfaceObserver.observe(this.el,{childList:!0,subtree:!0}),this.autoResize(),window.requestAnimationFrame(()=>this.scrollToBottom(!0))},updated(){this.syncScrollContainer(),this.syncExpandedSurfaces(),this.autoResize(),window.requestAnimationFrame(()=>this.scrollToBottom())},destroyed(){this.surfaceObserver.disconnect(),this.el.removeEventListener("input",this.handleInput),this.el.removeEventListener("keydown",this.handleKeyDown),this.scrollContainer&&this.scrollContainer.removeEventListener("scroll",this.handleScroll)}};var Ds={mounted(){this._onClickAway=e=>{this.el.contains(e.target)||this.el.querySelector(".colour-picker-popover")?.classList.add("hidden")},document.addEventListener("mousedown",this._onClickAway),this._setupCustomInput()},updated(){this._setupCustomInput()},destroyed(){document.removeEventListener("mousedown",this._onClickAway)},_setupCustomInput(){let e=this.el.querySelector(".colour-picker-custom input");if(!e||e._cpBound)return;e._cpBound=!0;let t=()=>{let i=e.value.trim();if(i&&!i.startsWith("#")&&(i="#"+i),/^#[0-9a-fA-F]{6}$/.test(i)){let s=this.el.dataset.pickEvent;this.pushEventTo(this.el.dataset.target,s,{color:i}),this.el.querySelector(".colour-picker-popover")?.classList.add("hidden")}};e.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),t())}),e.addEventListener("blur",t)}};var Os={mounted(){this.dragItemId=null,this.dragSourceEl=null,this.dropTargetEl=null,this.dropPosition=null,this.clearDropTarget=()=>{this.dropTargetEl&&this.dropTargetEl.classList.remove("is-drop-before","is-drop-after","is-drop-inside"),this.dropTargetEl=null,this.dropPosition=null},this.setDropTarget=(e,t)=>{this.dropTargetEl===e&&this.dropPosition===t||(this.clearDropTarget(),this.dropTargetEl=e,this.dropPosition=t,e.classList.add(`is-drop-${t}`))},this.handleDragStart=e=>{let t=e.target.closest("[data-menu-drag-handle='true']"),i=e.target.closest("[data-menu-item-id]");!t||!i||!this.el.contains(i)||(this.dragItemId=i.dataset.menuItemId||null,this.dragSourceEl=i,i.classList.add("is-dragging"),e.dataTransfer&&(e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",this.dragItemId||"")))},this.handleDragOver=e=>{let t=e.target.closest("[data-menu-item-id]");if(!this.dragItemId||!t||!this.el.contains(t)){this.clearDropTarget();return}let i=t.dataset.menuItemId||"";if(!i||i===this.dragItemId){this.clearDropTarget();return}e.preventDefault();let s=t.getBoundingClientRect(),n=e.clientY-s.top,r=t.dataset.menuCanDropInside==="true",o=s.height*.3,a=s.height*.7,l=r&&n>=o&&n<=a?"inside":n<s.height/2?"before":"after";this.setDropTarget(t,l),e.dataTransfer&&(e.dataTransfer.dropEffect="move")},this.handleDrop=e=>{let t=e.target.closest("[data-menu-item-id]");if(!this.dragItemId||!t||!this.el.contains(t)||!this.dropPosition){this.clearDropTarget();return}e.preventDefault(),this.pushEvent("menu_editor_drop_item",{drag_item_id:this.dragItemId,target_item_id:t.dataset.menuItemId,position:this.dropPosition}),this.clearDropTarget()},this.handleDragLeave=e=>{let t=e.relatedTarget;this.dropTargetEl&&(!t||!this.dropTargetEl.contains(t))&&this.clearDropTarget()},this.handleDragEnd=()=>{this.dragSourceEl&&this.dragSourceEl.classList.remove("is-dragging"),this.dragItemId=null,this.dragSourceEl=null,this.clearDropTarget()},this.el.addEventListener("dragstart",this.handleDragStart),this.el.addEventListener("dragover",this.handleDragOver),this.el.addEventListener("drop",this.handleDrop),this.el.addEventListener("dragleave",this.handleDragLeave),this.el.addEventListener("dragend",this.handleDragEnd)},destroyed(){this.el.removeEventListener("dragstart",this.handleDragStart),this.el.removeEventListener("dragover",this.handleDragOver),this.el.removeEventListener("drop",this.handleDrop),this.el.removeEventListener("dragleave",this.handleDragLeave),this.el.removeEventListener("dragend",this.handleDragEnd)}};var Pr={ArrowLeft:"cursorLeft",ArrowRight:"cursorRight",ArrowUp:"cursorUp",ArrowDown:"cursorDown"},Rr=()=>{let e=globalThis.navigator?.userAgent||"";return e.includes("AppleWebKit")&&!e.includes("Chrome")&&!e.includes("Chromium")},Lr=e=>e instanceof HTMLTextAreaElement&&e.classList.contains("inputarea"),Ir=e=>{if(e?.altKey||e?.ctrlKey||e?.metaKey)return null;let t=Pr[e?.key];return t?e.shiftKey?`${t}Select`:t:null},Dr=(e,t)=>{if(!e||!Lr(t.target))return!1;let i=Ir(t);return i?(t.preventDefault(),t.stopPropagation(),e.trigger("keyboard",i,{source:"k
Line truncated
|
`)){t.value=t.value.replace(/\n/g,""),this._prevInputValue=t.value,this.stickToBottom=!0,this.autoResize(),this._submitChat();return}this._enterKeyHandled=!1,this._prevInputValue=t.value,this.stickToBottom=!0,this.autoResize()},this.handleKeyDown=e=>{e.target.closest(".chat-input")&&e.key==="Enter"&&!e.shiftKey&&!e.isComposing&&(e.preventDefault(),this._enterKeyHandled=!0,this._submitChat())},this.el.addEventListener("input",this.handleInput),this.el.addEventListener("keydown",this.handleKeyDown),this.syncScrollContainer(),this.syncExpandedSurfaces(),this.surfaceObserver.observe(this.el,{childList:!0,subtree:!0}),this.autoResize(),window.requestAnimationFrame(()=>this.scrollToBottom(!0))},updated(){this.syncScrollContainer(),this.syncExpandedSurfaces(),this.autoResize(),window.requestAnimationFrame(()=>this.scrollToBottom())},destroyed(){this.surfaceObserver.disconnect(),this.el.removeEventListener("input",this.handleInput),this.el.removeEventListener("keydown",this.handleKeyDown),this.scrollContainer&&this.scrollContainer.removeEventListener("scroll",this.handleScroll)}};var Ds={mounted(){this._onClickAway=e=>{this.el.contains(e.target)||this.el.querySelector(".colour-picker-popover")?.classList.add("hidden")},document.addEventListener("mousedown",this._onClickAway),this._setupCustomInput()},updated(){this._setupCustomInput()},destroyed(){document.removeEventListener("mousedown",this._onClickAway)},_setupCustomInput(){let e=this.el.querySelector(".colour-picker-custom input");if(!e||e._cpBound)return;e._cpBound=!0;let t=()=>{let i=e.value.trim();if(i&&!i.startsWith("#")&&(i="#"+i),/^#[0-9a-fA-F]{6}$/.test(i)){let s=this.el.dataset.pickEvent;this.pushEventTo(this.el.dataset.target,s,{color:i}),this.el.querySelector(".colour-picker-popover")?.classList.add("hidden")}};e.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),t())}),e.addEventListener("blur",t)}};var Os={mounted(){this.dragItemId=null,this.dragSourceEl=null,this.dropTargetEl=null,this.dropPosition=null,this.clearDropTarget=()=>{this.dropTargetEl&&this.dropTargetEl.classList.remove("is-drop-before","is-drop-after","is-drop-inside"),this.dropTargetEl=null,this.dropPosition=null},this.setDropTarget=(e,t)=>{this.dropTargetEl===e&&this.dropPosition===t||(this.clearDropTarget(),this.dropTargetEl=e,this.dropPosition=t,e.classList.add(`is-drop-${t}`))},this.handleDragStart=e=>{let t=e.target.closest("[data-menu-drag-handle='true']"),i=e.target.closest("[data-menu-item-id]");!t||!i||!this.el.contains(i)||(this.dragItemId=i.dataset.menuItemId||null,this.dragSourceEl=i,i.classList.add("is-dragging"),e.dataTransfer&&(e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",this.dragItemId||"")))},this.handleDragOver=e=>{let t=e.target.closest("[data-menu-item-id]");if(!this.dragItemId||!t||!this.el.contains(t)){this.clearDropTarget();return}let i=t.dataset.menuItemId||"";if(!i||i===this.dragItemId){this.clearDropTarget();return}e.preventDefault();let s=t.getBoundingClientRect(),n=e.clientY-s.top,r=t.dataset.menuCanDropInside==="true",o=s.height*.3,a=s.height*.7,l=r&&n>=o&&n<=a?"inside":n<s.height/2?"before":"after";this.setDropTarget(t,l),e.dataTransfer&&(e.dataTransfer.dropEffect="move")},this.handleDrop=e=>{let t=e.target.closest("[data-menu-item-id]");if(!this.dragItemId||!t||!this.el.contains(t)||!this.dropPosition){this.clearDropTarget();return}e.preventDefault(),this.pushEvent("menu_editor_drop_item",{drag_item_id:this.dragItemId,target_item_id:t.dataset.menuItemId,position:this.dropPosition}),this.clearDropTarget()},this.handleDragLeave=e=>{let t=e.relatedTarget;this.dropTargetEl&&(!t||!this.dropTargetEl.contains(t))&&this.clearDropTarget()},this.handleDragEnd=()=>{this.dragSourceEl&&this.dragSourceEl.classList.remove("is-dragging"),this.dragItemId=null,this.dragSourceEl=null,this.clearDropTarget()},this.el.addEventListener("dragstart",this.handleDragStart),this.el.addEventListener("dragover",this.handleDragOver),this.el.addEventListener("drop",this.handleDrop),this.el.addEventListener("dragleave",this.handleDragLeave),this.el.addEventListener("dragend",this.handleDragEnd)},destroyed(){this.el.removeEventListener("dragstart",this.handleDragStart),this.el.removeEventListener("dragover",this.handleDragOver),this.el.removeEventListener("drop",this.handleDrop),this.el.removeEventListener("dragleave",this.handleDragLeave),this.el.removeEventListener("dragend",this.handleDragEnd)}};var Pr={ArrowLeft:"cursorLeft",ArrowRight:"cursorRight",ArrowUp:"cursorUp",ArrowDown:"cursorDown"},Rr=()=>{let e=globalThis.navigator?.userAgent||"";return e.includes("AppleWebKit")&&!e.includes("Chrome")&&!e.includes("Chromium")},Lr=e=>e instanceof HTMLTextAreaElement&&e.classList.contains("inputarea"),Ir=e=>{if(e?.altKey||e?.ctrlKey||e?.metaKey)return null;let t=Pr[e?.key];return t?e.shiftKey?`${t}Select`:t:null},Dr=(e,t)=>{let i=e.getSelections?e.getSelections():null;e.setValue(t),i&&i.length>0&&e.setSelections&&e.setSelections(i)},Or=(e,t)=>{if
Line truncated
|
||||||
`)?`
|
`)?`
|
||||||
`:"",d=l!==""&&!t.endsWith(`
|
`:"",d=l!==""&&!t.endsWith(`
|
||||||
`)?`
|
`)?`
|
||||||
`:"",p=`${h}${t}${d}`;this.editor.executeEdits("bds-insert-content",[{range:s,text:p,forceMoveMarkers:!0}]),this.editor.focus()},$e().then(async e=>{!this.host||!this.textarea||(await this.waitForMonacoVisibleSize(),ce(e),this.editor=e.editor.create(this.host,{value:this.textarea.value||"",language:this.language,theme:"bds-theme",automaticLayout:!0,minimap:{enabled:!1},scrollBeyondLastLine:!1,wordWrap:this.wordWrap,lineNumbers:"on",lineNumbersMinChars:3,fontSize:14,fontFamily:"'Cascadia Code', 'Consolas', 'Courier New', monospace",padding:{top:12,bottom:12},roundedSelection:!1,renderLineHighlight:"line",formatOnPaste:!0,cursorStyle:"line",cursorBlinking:"smooth",quickSuggestions:this.language!=="markdown-with-macros",tabSize:2,insertSpaces:!0}),As(this.editorId||this.el.id,this.editor),e.editor.setTheme("bds-theme"),this.syncEditorFromTextarea(),this.layoutEditorSoon(),this.changeSubscription=this.editor.onDidChangeModelContent(()=>{this.isApplyingRemoteUpdate||this.queueSync()}),this.insertEvent&&this.handleEvent(this.insertEvent,this.handleInsert),this.dropEvent&&(this.el.addEventListener("dragover",this.handleDragOver),this.el.addEventListener("drop",this.handleDrop)),Rr()&&this.host.addEventListener("keydown",this.handleWebKitTextAreaArrowKey,!0))}).catch(e=>{console.error("Failed to load Monaco editor",e)})},updated(){this.textarea=document.getElementById(this.el.dataset.monacoInputId)||this.el.querySelector("textarea"),this.host=this.el.querySelector(".monaco-editor-instance"),this.language=this.el.dataset.monacoLanguage||this.language||"plaintext",this.wordWrap=this.el.dataset.monacoWordWrap||this.wordWrap||"off",!(!this.editor||!this.textarea)&&($e().then(e=>{ce(e),e.editor.setTheme("bds-theme"),this.editor.getModel()?.getLanguageId()!==this.language&&e.editor.setModelLanguage(this.editor.getModel(),this.language),this.editor.updateOptions({wordWrap:this.wordWrap})}),this.syncEditorFromTextarea(),this.layoutEditorSoon())},destroyed(){window.clearTimeout(this.syncTimer),this.visibleSizeObserver?.disconnect(),this.changeSubscription?.dispose(),this.dropEvent&&(this.el.removeEventListener("dragover",this.handleDragOver),this.el.removeEventListener("drop",this.handleDrop)),this.host?.removeEventListener("keydown",this.handleWebKitTextAreaArrowKey,!0),ks(this.editorId||this.el.id),this.editor?.dispose()}};var Hs={mounted(){this.host=this.el.querySelector(".monaco-diff-editor-instance"),this.originalInput=this.el.querySelector(".monaco-diff-original"),this.modifiedInput=this.el.querySelector(".monaco-diff-modified"),this.filePath=this.el.dataset.monacoDiffFilePath||"working-tree",this.language=this.el.dataset.monacoDiffLanguage||"plaintext",this.viewStyle=this.el.dataset.monacoDiffViewStyle||"inline",this.wordWrap=this.el.dataset.monacoDiffWordWrap||"off",this.hideUnchanged=this.el.dataset.monacoDiffHideUnchanged==="true",this.readValues=()=>({original:this.originalInput?.value||"",modified:this.modifiedInput?.value||""}),this.applyDataset=()=>{this.filePath=this.el.dataset.monacoDiffFilePath||"working-tree",this.language=this.el.dataset.monacoDiffLanguage||"plaintext",this.viewStyle=this.el.dataset.monacoDiffViewStyle||"inline",this.wordWrap=this.el.dataset.monacoDiffWordWrap||"off",this.hideUnchanged=this.el.dataset.monacoDiffHideUnchanged==="true"},this.setModels=e=>{let t=this.readValues();this.originalModel?.dispose(),this.modifiedModel?.dispose(),this.originalModel=e.editor.createModel(t.original,this.language,e.Uri.parse(ci(this.filePath,"original"))),this.modifiedModel=e.editor.createModel(t.modified,this.language,e.Uri.parse(ci(this.filePath,"modified"))),this.editor.setModel({original:this.originalModel,modified:this.modifiedModel}),this.lastFilePath=this.filePath},$e().then(e=>{this.host&&(ce(e),this.editor=e.editor.createDiffEditor(this.host,{theme:"bds-theme",automaticLayout:!0,readOnly:!0,renderSideBySide:this.viewStyle==="side-by-side",minimap:{enabled:!1},scrollBeyondLastLine:!1,lineNumbers:"on",diffCodeLens:!1,originalEditable:!1,wordWrap:this.wordWrap,hideUnchangedRegions:{enabled:this.hideUnchanged},ignoreTrimWhitespace:!1}),this.setModels(e))}).catch(e=>{console.error("Failed to load Monaco diff editor",e)})},updated(){this.host=this.el.querySelector(".monaco-diff-editor-instance"),this.originalInput=this.el.querySelector(".monaco-diff-original"),this.modifiedInput=this.el.querySelector(".monaco-diff-modified"),this.applyDataset(),this.editor&&$e().then(e=>{if(ce(e),e.editor.setTheme("bds-theme"),this.editor.updateOptions({renderSideBySide:this.viewStyle==="side-by-side",wordWrap:this.wordWrap,hideUnchangedRegions:{enabled:this.hideUnchanged}}),this.lastFilePath!==this.filePath){this.setModels(e);return}let t=this.readValues();this.originalModel&&this.originalModel.getLanguageId()!==this.language&&e.editor.setModelLanguage(this.originalModel,this.language),this.modifiedModel&&this.modifiedModel.getLanguageId()!==this.language&&e.editor.setModelLanguage(this.modifiedModel,this.language
Line truncated
|
`:"",p=`${h}${t}${d}`;this.editor.executeEdits("bds-insert-content",[{range:s,text:p,forceMoveMarkers:!0}]),this.editor.focus()},$e().then(async e=>{!this.host||!this.textarea||(await this.waitForMonacoVisibleSize(),ce(e),this.editor=e.editor.create(this.host,{value:this.textarea.value||"",language:this.language,theme:"bds-theme",automaticLayout:!0,minimap:{enabled:!1},scrollBeyondLastLine:!1,wordWrap:this.wordWrap,lineNumbers:"on",lineNumbersMinChars:3,fontSize:14,fontFamily:"'Cascadia Code', 'Consolas', 'Courier New', monospace",padding:{top:12,bottom:12},roundedSelection:!1,renderLineHighlight:"line",formatOnPaste:!0,cursorStyle:"line",cursorBlinking:"smooth",quickSuggestions:this.language!=="markdown-with-macros",tabSize:2,insertSpaces:!0}),As(this.editorId||this.el.id,this.editor),e.editor.setTheme("bds-theme"),this.syncEditorFromTextarea(),this.layoutEditorSoon(),this.changeSubscription=this.editor.onDidChangeModelContent(()=>{this.isApplyingRemoteUpdate||this.queueSync()}),this.insertEvent&&this.handleEvent(this.insertEvent,this.handleInsert),this.dropEvent&&(this.el.addEventListener("dragover",this.handleDragOver),this.el.addEventListener("drop",this.handleDrop)),Rr()&&this.host.addEventListener("keydown",this.handleWebKitTextAreaArrowKey,!0))}).catch(e=>{console.error("Failed to load Monaco editor",e)})},updated(){this.textarea=document.getElementById(this.el.dataset.monacoInputId)||this.el.querySelector("textarea"),this.host=this.el.querySelector(".monaco-editor-instance"),this.language=this.el.dataset.monacoLanguage||this.language||"plaintext",this.wordWrap=this.el.dataset.monacoWordWrap||this.wordWrap||"off",!(!this.editor||!this.textarea)&&($e().then(e=>{ce(e),e.editor.setTheme("bds-theme"),this.editor.getModel()?.getLanguageId()!==this.language&&e.editor.setModelLanguage(this.editor.getModel(),this.language),this.editor.updateOptions({wordWrap:this.wordWrap})}),this.syncEditorFromTextarea(),this.layoutEditorSoon())},destroyed(){window.clearTimeout(this.syncTimer),this.visibleSizeObserver?.disconnect(),this.changeSubscription?.dispose(),this.dropEvent&&(this.el.removeEventListener("dragover",this.handleDragOver),this.el.removeEventListener("drop",this.handleDrop)),this.host?.removeEventListener("keydown",this.handleWebKitTextAreaArrowKey,!0),ks(this.editorId||this.el.id),this.editor?.dispose()}};var Hs={mounted(){this.host=this.el.querySelector(".monaco-diff-editor-instance"),this.originalInput=this.el.querySelector(".monaco-diff-original"),this.modifiedInput=this.el.querySelector(".monaco-diff-modified"),this.filePath=this.el.dataset.monacoDiffFilePath||"working-tree",this.language=this.el.dataset.monacoDiffLanguage||"plaintext",this.viewStyle=this.el.dataset.monacoDiffViewStyle||"inline",this.wordWrap=this.el.dataset.monacoDiffWordWrap||"off",this.hideUnchanged=this.el.dataset.monacoDiffHideUnchanged==="true",this.readValues=()=>({original:this.originalInput?.value||"",modified:this.modifiedInput?.value||""}),this.applyDataset=()=>{this.filePath=this.el.dataset.monacoDiffFilePath||"working-tree",this.language=this.el.dataset.monacoDiffLanguage||"plaintext",this.viewStyle=this.el.dataset.monacoDiffViewStyle||"inline",this.wordWrap=this.el.dataset.monacoDiffWordWrap||"off",this.hideUnchanged=this.el.dataset.monacoDiffHideUnchanged==="true"},this.setModels=e=>{let t=this.readValues();this.originalModel?.dispose(),this.modifiedModel?.dispose(),this.originalModel=e.editor.createModel(t.original,this.language,e.Uri.parse(ci(this.filePath,"original"))),this.modifiedModel=e.editor.createModel(t.modified,this.language,e.Uri.parse(ci(this.filePath,"modified"))),this.editor.setModel({original:this.originalModel,modified:this.modifiedModel}),this.lastFilePath=this.filePath},$e().then(e=>{this.host&&(ce(e),this.editor=e.editor.createDiffEditor(this.host,{theme:"bds-theme",automaticLayout:!0,readOnly:!0,renderSideBySide:this.viewStyle==="side-by-side",minimap:{enabled:!1},scrollBeyondLastLine:!1,lineNumbers:"on",diffCodeLens:!1,originalEditable:!1,wordWrap:this.wordWrap,hideUnchangedRegions:{enabled:this.hideUnchanged},ignoreTrimWhitespace:!1}),this.setModels(e))}).catch(e=>{console.error("Failed to load Monaco diff editor",e)})},updated(){this.host=this.el.querySelector(".monaco-diff-editor-instance"),this.originalInput=this.el.querySelector(".monaco-diff-original"),this.modifiedInput=this.el.querySelector(".monaco-diff-modified"),this.applyDataset(),this.editor&&$e().then(e=>{if(ce(e),e.editor.setTheme("bds-theme"),this.editor.updateOptions({renderSideBySide:this.viewStyle==="side-by-side",wordWrap:this.wordWrap,hideUnchangedRegions:{enabled:this.hideUnchanged}}),this.lastFilePath!==this.filePath){this.setModels(e);return}let t=this.readValues();this.originalModel&&this.originalModel.getLanguageId()!==this.language&&e.editor.setModelLanguage(this.originalModel,this.language),this.modifiedModel&&this.modifiedModel.getLanguageId()!==this.language&&e.editor.setModelLanguage(this.modifiedModel,this.language
Line truncated
|
||||||
@@ -3427,6 +3427,115 @@ defmodule BDS.Desktop.ShellLiveTest do
|
|||||||
assert output_html =~ "Automatic AI actions stay gated by airplane mode"
|
assert output_html =~ "Automatic AI actions stay gated by airplane mode"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "selecting an internal insert link result pushes the link and closes the overlay", %{
|
||||||
|
project: project
|
||||||
|
} do
|
||||||
|
{:ok, target} =
|
||||||
|
Posts.create_post(%{project_id: project.id, title: "Link Target", content: "Target body"})
|
||||||
|
|
||||||
|
{:ok, post} =
|
||||||
|
Posts.create_post(%{project_id: project.id, title: "Editing Post", content: "Body"})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
render_click(view, "pin_sidebar_item", %{
|
||||||
|
"route" => "post",
|
||||||
|
"id" => post.id,
|
||||||
|
"title" => post.title,
|
||||||
|
"subtitle" => "draft"
|
||||||
|
})
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("[phx-click='open_overlay'][phx-value-kind='insert_link']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
assert html =~ "insert-modal"
|
||||||
|
|
||||||
|
render_click(view, "overlay_select_result", %{"id" => target.id})
|
||||||
|
assert_push_event(view, "post-editor-insert-content", %{id: _id, content: content})
|
||||||
|
assert content =~ "Link Target"
|
||||||
|
|
||||||
|
refute render(view) =~ "insert-modal"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "inserting an external link pushes the link and closes the overlay", %{project: project} do
|
||||||
|
{:ok, post} =
|
||||||
|
Posts.create_post(%{project_id: project.id, title: "Editing Post", content: "Body"})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
render_click(view, "pin_sidebar_item", %{
|
||||||
|
"route" => "post",
|
||||||
|
"id" => post.id,
|
||||||
|
"title" => post.title,
|
||||||
|
"subtitle" => "draft"
|
||||||
|
})
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("[phx-click='open_overlay'][phx-value-kind='insert_link']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
assert html =~ "insert-modal"
|
||||||
|
|
||||||
|
render_click(view, "overlay_set_tab", %{"tab" => "external"})
|
||||||
|
|
||||||
|
render_change(view, "overlay_update_form", %{
|
||||||
|
"overlay" => %{"url" => "https://example.com", "text" => "Example"}
|
||||||
|
})
|
||||||
|
|
||||||
|
render_click(view, "overlay_insert_external", %{})
|
||||||
|
assert_push_event(view, "post-editor-insert-content", %{content: "[Example](https://example.com)"})
|
||||||
|
|
||||||
|
refute render(view) =~ "insert-modal"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "selecting an insert media result pushes the syntax and closes the overlay", %{
|
||||||
|
project: project
|
||||||
|
} do
|
||||||
|
temp_dir =
|
||||||
|
Path.join(System.tmp_dir!(), "bds-shell-live-#{System.unique_integer([:positive])}")
|
||||||
|
|
||||||
|
File.mkdir_p!(temp_dir)
|
||||||
|
media_source_path = Path.join(temp_dir, "insert-media.jpg")
|
||||||
|
File.write!(media_source_path, "fake image body")
|
||||||
|
|
||||||
|
{:ok, media} =
|
||||||
|
Media.import_media(%{
|
||||||
|
project_id: project.id,
|
||||||
|
source_path: media_source_path,
|
||||||
|
title: "Insert Media"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, post} =
|
||||||
|
Posts.create_post(%{project_id: project.id, title: "Editing Post", content: "Body"})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
render_click(view, "pin_sidebar_item", %{
|
||||||
|
"route" => "post",
|
||||||
|
"id" => post.id,
|
||||||
|
"title" => post.title,
|
||||||
|
"subtitle" => "draft"
|
||||||
|
})
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> element("[phx-click='open_overlay'][phx-value-kind='insert_media']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
assert html =~ "insert-modal"
|
||||||
|
|
||||||
|
render_click(view, "overlay_set_search", %{"overlay" => %{"query" => "Insert"}})
|
||||||
|
render_click(view, "overlay_select_result", %{"id" => media.id})
|
||||||
|
|
||||||
|
assert_push_event(view, "post-editor-insert-content", %{content: content})
|
||||||
|
assert content =~ "bds-media://#{media.id}"
|
||||||
|
|
||||||
|
refute render(view) =~ "insert-modal"
|
||||||
|
end
|
||||||
|
|
||||||
test "ai suggestions overlay fetches async results for posts when online", %{project: project} do
|
test "ai suggestions overlay fetches async results for posts when online", %{project: project} do
|
||||||
Application.put_env(:bds, :test_pid, self())
|
Application.put_env(:bds, :test_pid, self())
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,19 @@ defmodule BDS.MacBundleTest do
|
|||||||
alias BDS.MacBundle.Dylibs
|
alias BDS.MacBundle.Dylibs
|
||||||
alias BDS.MacBundle.Icon
|
alias BDS.MacBundle.Icon
|
||||||
|
|
||||||
|
# Give a copied keg dylib a controlled id so verify_standalone only sees the
|
||||||
|
# reference under test, not the keg's absolute LC_ID_DYLIB.
|
||||||
|
defp fix_id!(dylib, id \\ nil) do
|
||||||
|
{_out, 0} =
|
||||||
|
System.cmd(
|
||||||
|
"install_name_tool",
|
||||||
|
Dylibs.id_args(dylib, id || "@loader_path/" <> Path.basename(dylib)),
|
||||||
|
stderr_to_stdout: true
|
||||||
|
)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
# Parse "#RRGGBB" into {r, g, b} for property assertions in tests.
|
# Parse "#RRGGBB" into {r, g, b} for property assertions in tests.
|
||||||
defp rgb("#" <> hex) do
|
defp rgb("#" <> hex) do
|
||||||
{r, g, b} = {String.slice(hex, 0, 2), String.slice(hex, 2, 2), String.slice(hex, 4, 2)}
|
{r, g, b} = {String.slice(hex, 0, 2), String.slice(hex, 2, 2), String.slice(hex, 4, 2)}
|
||||||
@@ -176,6 +189,116 @@ defmodule BDS.MacBundleTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Dylibs.resolve_rpath_dep/3" do
|
||||||
|
test "expands @loader_path rpaths against the referencing binary's directory" do
|
||||||
|
assert Dylibs.resolve_rpath_dep(
|
||||||
|
"@rpath/libsharpyuv.0.dylib",
|
||||||
|
["@loader_path/../lib", "/opt/homebrew/lib"],
|
||||||
|
"/opt/homebrew/opt/webp/lib"
|
||||||
|
) == [
|
||||||
|
"/opt/homebrew/opt/webp/lib/libsharpyuv.0.dylib",
|
||||||
|
"/opt/homebrew/lib/libsharpyuv.0.dylib"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "resolves in-tree loader-relative rpaths (vix/exla precompiled layout)" do
|
||||||
|
assert Dylibs.resolve_rpath_dep(
|
||||||
|
"@rpath/libvips.42.dylib",
|
||||||
|
["@loader_path/precompiled_libvips/lib"],
|
||||||
|
"/app/rel/lib/vix-0.38.0/priv"
|
||||||
|
) == ["/app/rel/lib/vix-0.38.0/priv/precompiled_libvips/lib/libvips.42.dylib"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips @executable_path rpaths (not resolvable from a library) and non-@rpath deps" do
|
||||||
|
assert Dylibs.resolve_rpath_dep(
|
||||||
|
"@rpath/libfoo.dylib",
|
||||||
|
["@executable_path/../Frameworks"],
|
||||||
|
"/any/dir"
|
||||||
|
) == []
|
||||||
|
|
||||||
|
assert Dylibs.resolve_rpath_dep("/usr/lib/libSystem.B.dylib", ["@loader_path"], "/d") == []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Dylibs.relink_changes/3 (@rpath deps)" do
|
||||||
|
test "rewrites @rpath deps whose basename was bundled, leaves the rest" do
|
||||||
|
deps = [
|
||||||
|
"/opt/homebrew/opt/webp/lib/libwebp.7.dylib",
|
||||||
|
"@rpath/libsharpyuv.0.dylib",
|
||||||
|
"@rpath/libvips.42.dylib",
|
||||||
|
"/usr/lib/libSystem.B.dylib"
|
||||||
|
]
|
||||||
|
|
||||||
|
assert Dylibs.relink_changes(deps, "@loader_path", ["libsharpyuv.0.dylib"]) == [
|
||||||
|
{"/opt/homebrew/opt/webp/lib/libwebp.7.dylib", "@loader_path/libwebp.7.dylib"},
|
||||||
|
{"@rpath/libsharpyuv.0.dylib", "@loader_path/libsharpyuv.0.dylib"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "MacBundle.verify_standalone/1 (@rpath resolution gate)" do
|
||||||
|
@describetag :macos_tools
|
||||||
|
|
||||||
|
# Regression for the July 2026 crash: Homebrew's libwebp references its
|
||||||
|
# sibling as @rpath/libsharpyuv.0.dylib (rpath @loader_path/../lib). A copy
|
||||||
|
# in Frameworks without the sibling must fail verification; a copy whose
|
||||||
|
# rpath resolves inside the tree must pass.
|
||||||
|
test "fails on an @rpath dep that does not resolve inside the bundle and passes when it does" do
|
||||||
|
keg_webp = "/opt/homebrew/opt/webp/lib/libwebp.7.dylib"
|
||||||
|
keg_sharpyuv = "/opt/homebrew/opt/webp/lib/libsharpyuv.0.dylib"
|
||||||
|
|
||||||
|
if File.exists?(keg_webp) and File.exists?(keg_sharpyuv) do
|
||||||
|
tmp = Path.join(System.tmp_dir!(), "bds-rpath-#{System.unique_integer([:positive])}")
|
||||||
|
on_exit(fn -> File.rm_rf!(tmp) end)
|
||||||
|
|
||||||
|
# Broken layout: libwebp in Frameworks, libsharpyuv missing entirely.
|
||||||
|
broken = Path.join(tmp, "broken/Contents/Frameworks")
|
||||||
|
File.mkdir_p!(broken)
|
||||||
|
File.cp!(keg_webp, Path.join(broken, "libwebp.7.dylib"))
|
||||||
|
File.chmod!(Path.join(broken, "libwebp.7.dylib"), 0o644)
|
||||||
|
fix_id!(Path.join(broken, "libwebp.7.dylib"))
|
||||||
|
|
||||||
|
assert {:error, {:external_refs, offenders}} =
|
||||||
|
MacBundle.verify_standalone(Path.join(tmp, "broken"))
|
||||||
|
|
||||||
|
assert Enum.any?(offenders, fn {_file, ref} -> ref == "@rpath/libsharpyuv.0.dylib" end)
|
||||||
|
|
||||||
|
# Healthy layout: @loader_path/../lib resolves next to the library, so
|
||||||
|
# placing the sibling there satisfies the reference (vix-style in-tree
|
||||||
|
# resolution must not be flagged). libsharpyuv keeps an @rpath/ self id
|
||||||
|
# — precompiled NIF deps (hnswlib, libvips, libmlx) ship exactly that,
|
||||||
|
# and a library's own LC_ID_DYLIB must never count as unresolved.
|
||||||
|
healthy = Path.join(tmp, "healthy/Contents/pkg/lib")
|
||||||
|
File.mkdir_p!(healthy)
|
||||||
|
File.cp!(keg_webp, Path.join(healthy, "libwebp.7.dylib"))
|
||||||
|
File.cp!(keg_sharpyuv, Path.join(healthy, "libsharpyuv.0.dylib"))
|
||||||
|
Enum.each(["libwebp.7.dylib", "libsharpyuv.0.dylib"], fn base ->
|
||||||
|
File.chmod!(Path.join(healthy, base), 0o644)
|
||||||
|
end)
|
||||||
|
|
||||||
|
fix_id!(Path.join(healthy, "libwebp.7.dylib"))
|
||||||
|
fix_id!(Path.join(healthy, "libsharpyuv.0.dylib"), "@rpath/libsharpyuv.0.dylib")
|
||||||
|
|
||||||
|
# Match the precompiled-NIF shape exactly: @rpath/ self id with NO
|
||||||
|
# LC_RPATH left to resolve it (hnswlib_nif.so ships like this).
|
||||||
|
sharpyuv = Path.join(healthy, "libsharpyuv.0.dylib")
|
||||||
|
|
||||||
|
{rpath_out, 0} = System.cmd("otool", ["-l", sharpyuv], stderr_to_stdout: true)
|
||||||
|
|
||||||
|
Enum.each(Dylibs.parse_rpaths(rpath_out), fn rpath ->
|
||||||
|
{_out, 0} =
|
||||||
|
System.cmd("install_name_tool", ["-delete_rpath", rpath, sharpyuv],
|
||||||
|
stderr_to_stdout: true
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert MacBundle.verify_standalone(Path.join(tmp, "healthy")) == :ok
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "Dylibs.parse_rpaths/1" do
|
describe "Dylibs.parse_rpaths/1" do
|
||||||
test "extracts LC_RPATH paths from otool -l output, ignoring other load commands" do
|
test "extracts LC_RPATH paths from otool -l output, ignoring other load commands" do
|
||||||
output = """
|
output = """
|
||||||
|
|||||||
@@ -400,6 +400,50 @@ defmodule BDS.UI.ShellTest do
|
|||||||
assert status == 0, output
|
assert status == 0, output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "monaco hook preserves cursor and selection when applying a remote value" do
|
||||||
|
script = """
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
class FakeTextArea {}
|
||||||
|
globalThis.HTMLTextAreaElement = FakeTextArea;
|
||||||
|
|
||||||
|
const { applyRemoteEditorValue } = await import("./assets/js/hooks/monaco_editor.js");
|
||||||
|
|
||||||
|
const selections = [{ startLineNumber: 3, startColumn: 5, endLineNumber: 3, endColumn: 9 }];
|
||||||
|
const calls = [];
|
||||||
|
const editor = {
|
||||||
|
getSelections: () => selections,
|
||||||
|
setValue: (value) => calls.push(["setValue", value]),
|
||||||
|
setSelections: (restored) => calls.push(["setSelections", restored])
|
||||||
|
};
|
||||||
|
|
||||||
|
applyRemoteEditorValue(editor, "line one\\nline two\\nline three");
|
||||||
|
|
||||||
|
assert.deepEqual(calls, [
|
||||||
|
["setValue", "line one\\nline two\\nline three"],
|
||||||
|
["setSelections", selections]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Editors without selections still get the value applied.
|
||||||
|
const bareCalls = [];
|
||||||
|
const bareEditor = {
|
||||||
|
getSelections: () => null,
|
||||||
|
setValue: (value) => bareCalls.push(["setValue", value]),
|
||||||
|
setSelections: () => bareCalls.push(["setSelections"])
|
||||||
|
};
|
||||||
|
|
||||||
|
applyRemoteEditorValue(bareEditor, "fresh");
|
||||||
|
assert.deepEqual(bareCalls, [["setValue", "fresh"]]);
|
||||||
|
"""
|
||||||
|
|
||||||
|
{output, status} =
|
||||||
|
System.cmd("node", ["--input-type=module", "--eval", script],
|
||||||
|
cd: "/Users/gb/Projects/bDS2",
|
||||||
|
stderr_to_stdout: true
|
||||||
|
)
|
||||||
|
|
||||||
|
assert status == 0, output
|
||||||
|
end
|
||||||
|
|
||||||
test "monaco ESM build config emits served worker bundles" do
|
test "monaco ESM build config emits served worker bundles" do
|
||||||
mix_exs = File.read!("/Users/gb/Projects/bDS2/mix.exs")
|
mix_exs = File.read!("/Users/gb/Projects/bDS2/mix.exs")
|
||||||
config = File.read!("/Users/gb/Projects/bDS2/config/config.exs")
|
config = File.read!("/Users/gb/Projects/bDS2/config/config.exs")
|
||||||
|
|||||||
Reference in new issue
Block a user