diff --git a/README.md b/README.md index ddf2b7f..2c72d29 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ The `WLXHarness/` directory contains a standalone test harness (contributed by N ## How It Works -MDView is a WLX lister plugin — a DLL that Total Commander loads when you press F3 on a matching file type. It contains a built-in Markdown-to-HTML converter and embeds an MSHTML (IE11) WebBrowser control to render the output. The rendered HTML is written to a temporary file in the same directory as the source `.md` file, allowing local relative image paths to resolve correctly via the Local Machine security zone. The split source view uses a Windows RichEdit control with synchronised ratio-based scrolling. Keyboard input is handled by subclassing the browser's internal window and (when active) the RichEdit control. Settings are persisted via TC's standard INI file mechanism. +MDView is a WLX lister plugin — a DLL that Total Commander loads when you press F3 on a matching file type. It contains a built-in Markdown-to-HTML converter and embeds an MSHTML (IE11) WebBrowser control to render the output. The rendered HTML is written to a temporary file in the system temp directory, and a `` tag is added so that local relative image paths still resolve correctly against the source `.md` directory. The split source view uses a Windows RichEdit control with synchronised ratio-based scrolling. Keyboard input is handled by subclassing the browser's internal window and (when active) the RichEdit control. Settings are persisted via TC's standard INI file mechanism. ## Credits diff --git a/mdview.c b/mdview.c index 23f6a9a..a0781b5 100644 --- a/mdview.c +++ b/mdview.c @@ -1580,44 +1580,108 @@ static HRESULT create_browser(HWND hwnd, IWebBrowser2** ppB, IOleObject** ppO, S *ppB=pB; *ppO=pO; return S_OK; } +/* Build a tag pointing to the source markdown directory so that + relative image/link URLs still resolve after the temp file is moved. */ +static char* dir_to_base_tag(const WCHAR* dir) { + if (!dir || !dir[0]) return NULL; + int len = WideCharToMultiByte(CP_UTF8, 0, dir, -1, NULL, 0, NULL, NULL); + if (len <= 0) return NULL; + char* path = (char*)malloc((size_t)len); + if (!path) return NULL; + WideCharToMultiByte(CP_UTF8, 0, dir, -1, path, len, NULL, NULL); + size_t pl = strlen(path); + int need_slash = (pl == 0 || (path[pl-1] != '\\' && path[pl-1] != '/')); + size_t tag_size = pl + 64 + (need_slash ? 1 : 0); + char* tag = (char*)malloc(tag_size); + if (!tag) { free(path); return NULL; } + for (size_t i = 0; i < pl; i++) { + if (path[i] == '\\') path[i] = '/'; + } + snprintf(tag, tag_size, "", path, need_slash ? "/" : ""); + free(path); + return tag; +} + +/* Insert a string immediately before the first tag. */ +static char* insert_before_head_end(const char* html, const char* insert) { + const char* head_end = strstr(html, ""); + if (!head_end || !insert || !insert[0]) { + size_t len = strlen(html); + char* copy = (char*)malloc(len + 1); + if (copy) memcpy(copy, html, len + 1); + return copy; + } + size_t before = (size_t)(head_end - html); + size_t inslen = strlen(insert); + size_t after = strlen(head_end); + char* out = (char*)malloc(before + inslen + after + 1); + if (!out) { + size_t len = strlen(html); + char* copy = (char*)malloc(len + 1); + if (copy) memcpy(copy, html, len + 1); + return copy; + } + memcpy(out, html, before); + memcpy(out + before, insert, inslen); + memcpy(out + before + inslen, head_end, after + 1); + return out; +} + static void navigate_to_html(IWebBrowser2* pB, const char* html, const WCHAR* dir, WCHAR* outTempPath) { outTempPath[0] = 0; - /* Write HTML to a temp file in the same directory as the .md file. - This makes MSHTML load in the Local Machine zone so file:// images work. */ - WCHAR tempPath[MAX_PATH]; - wcscpy(tempPath, dir); - /* Append a unique temp filename */ - WCHAR tempName[64]; - wsprintfW(tempName, L"_mdview_%08x.html", GetTickCount()); - wcscat(tempPath, tempName); - - /* Write UTF-8 HTML with BOM and Mark of the Web */ - FILE* tf = _wfopen(tempPath, L"wb"); - if (tf) { - /* UTF-8 BOM */ - fputc(0xEF, tf); fputc(0xBB, tf); fputc(0xBF, tf); - /* Mark of the Web — tells MSHTML to allow script execution in local files */ - fprintf(tf, "\r\n"); - fwrite(html, 1, strlen(html), tf); - fclose(tf); - wcscpy(outTempPath, tempPath); - - /* Navigate to the temp file */ - VARIANT ve; VariantInit(&ve); - BSTR url = SysAllocString(tempPath); - IWebBrowser2_Navigate(pB, url, &ve, &ve, &ve, &ve); - SysFreeString(url); - - /* Wait for load */ - READYSTATE rs; int to = 200; - do { MSG msg; while(PeekMessageW(&msg,NULL,0,0,PM_REMOVE)){TranslateMessage(&msg);DispatchMessageW(&msg);} - IWebBrowser2_get_ReadyState(pB,&rs); - if(rs!=READYSTATE_COMPLETE) Sleep(10); - } while(rs!=READYSTATE_COMPLETE && --to>0); - return; + /* Add a tag so relative URLs resolve against the markdown directory. */ + char* base_tag = dir_to_base_tag(dir); + char* final_html = insert_before_head_end(html, base_tag ? base_tag : ""); + if (base_tag) free(base_tag); + + /* Build a unique temp file path in the system temp directory. */ + WCHAR tempPath[MAX_PATH] = {0}; + int have_temp = 0; + DWORD gp = GetTempPathW(MAX_PATH, tempPath); + if (gp && gp <= MAX_PATH) { + size_t tlen = wcslen(tempPath); + if (tlen > 0 && tempPath[tlen-1] != L'\\' && tempPath[tlen-1] != L'/') { + if (tlen < MAX_PATH - 1) { wcscat(tempPath, L"\\"); tlen++; } + } + WCHAR tempName[64]; + wsprintfW(tempName, L"_mdview_%08x_%u.html", GetTickCount(), GetCurrentProcessId()); + if (tlen + wcslen(tempName) < MAX_PATH) { + wcscat(tempPath, tempName); + have_temp = 1; + } } + if (have_temp) { + FILE* tf = _wfopen(tempPath, L"wb"); + if (tf) { + /* UTF-8 BOM */ + fputc(0xEF, tf); fputc(0xBB, tf); fputc(0xBF, tf); + /* Mark of the Web — tells MSHTML to allow script execution in local files */ + fprintf(tf, "\r\n"); + fwrite(final_html, 1, strlen(final_html), tf); + fclose(tf); + free(final_html); + wcscpy(outTempPath, tempPath); + + /* Navigate to the temp file */ + VARIANT ve; VariantInit(&ve); + BSTR url = SysAllocString(tempPath); + IWebBrowser2_Navigate(pB, url, &ve, &ve, &ve, &ve); + SysFreeString(url); + + /* Wait for load */ + READYSTATE rs; int to = 200; + do { MSG msg; while(PeekMessageW(&msg,NULL,0,0,PM_REMOVE)){TranslateMessage(&msg);DispatchMessageW(&msg);} + IWebBrowser2_get_ReadyState(pB,&rs); + if(rs!=READYSTATE_COMPLETE) Sleep(10); + } while(rs!=READYSTATE_COMPLETE && --to>0); + return; + } + } + + free(final_html); + /* Fallback: about:blank + document.write (no local image support) */ VARIANT ve; VariantInit(&ve); BSTR url=SysAllocString(L"about:blank"); @@ -1798,7 +1862,7 @@ __declspec(dllexport) HWND __stdcall ListLoadW(HWND pw, WCHAR* file, int flags) layout_views(data); IWebBrowser2_put_Silent(data->pBrowser, VARIANT_TRUE); - /* Extract directory from file path for temp file placement */ + /* Extract directory from file path for the tag */ WCHAR fileDir[MAX_PATH]; wcsncpy(fileDir, file, MAX_PATH); fileDir[MAX_PATH-1]=0; WCHAR* lastSep = wcsrchr(fileDir, L'\\'); diff --git a/test.md b/test.md index 2e682bf..607c6df 100644 --- a/test.md +++ b/test.md @@ -261,9 +261,18 @@ This is a reference-style image: ![Placeholder][sample-img] +This is a local image loaded from the same directory as this markdown file. It should render correctly even though the temp HTML file is created in the system temp directory, because a `` tag points back at this directory: + +![Local image](test_local.png) + +This is a reference-style local image: + +![Local reference image][local-img] + This is an implicit reference link to [Google]. [md-guide]: https://www.markdownguide.org "Official Markdown Guide" [sample-img]: https://placehold.co/200x100.png "A test placeholder" +[local-img]: test_local.png "Local test image" [Google]: https://www.google.com "Google Search" diff --git a/test_local.png b/test_local.png new file mode 100644 index 0000000..c7122f3 Binary files /dev/null and b/test_local.png differ