Xiaoji Blog Technical Notes

website·Indexed 29 days ago

https://xiaojiblog.netlify.app/

A collection of technical notes and implementation guides covering web development (HTML, CSS, JavaScript, TypeScript), database management (MySQL, Prisma), and backend frameworks like ThinkPHP 8.x. Includes practical examples for UI components, API request handling, and Tampermonkey script development.

Tokens
177.6K
Snippets
873
Records
956
Agent score
75%

What's inside Xiaoji Blog

  1. Troubleshoot Editor.md image upload SecurityError in cross-origin environments

    When using Editor.md in a decoupled architecture (frontend and backend on different domains/ports), you may encounter the error: SecurityError: Permission denied to access property “document” on cross-origin object during image uploads.

    This occurs because Editor.md uses a hidden iframe to handle the image upload via a standard HTML form submission. After the upload, the library attempts to access the response body of that iframe to retrieve the uploaded image URL. If the imageUploadURL points to a different origin than the page, the browser blocks access to the iframe's document object due to the Same-Origin Policy.

    // The problematic line in image-dialog.js causing the SecurityError:
    var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body;
  2. Implement secure communication using a Preload Script

    For a more secure architecture, use a preload script to bridge the main process (Node.js environment) and the renderer process (Browser environment). Use contextBridge to expose specific APIs to the renderer and ipcRenderer/ipcMain for asynchronous communication.
    // 1. main.js: Configure preload and handle IPC events
    const { app, BrowserWindow, ipcMain } = require('electron')
    const path = require('path')
    const fs = require('fs')
    
    function createWindow() {
      let win = new BrowserWindow({
        webPreferences: {
          preload: path.resolve(__dirname, './preload.js')
        }
      })
    
      ipcMain.on('save-file', (event, data) => {
        fs.writeFileSync('D:/hello.txt', data)
      })
    
      ipcMain.handle('read-file', () => {
        return fs.readFileSync('D:/hello.txt').toString()
      })
      
      win.loadFile('./pages/index.html')
    }
    
    // 2. preload.js: Expose API to renderer
    const { contextBridge, ipcRenderer } = require('electron')
    
    contextBridge.exposeInMainWorld('myAPI', {
      saveFile: (data) => ipcRenderer.send('save-file', data),
      readFile: () => ipcRenderer.invoke('read-file')
    })
    
    // 3. render.js: Call the exposed API
    window.myAPI.saveFile('Hello from renderer')
    window.myAPI.readFile().then(content => console.log(content))
  3. Use Type Assertions to override TypeScript's type checking

    Type assertions (using the as keyword) tell TypeScript that a value is of a specific type, even if TypeScript's static analysis suggests it might be something else (like undefined). This is useful when you know more about the value's type than the compiler does.
    let arr = [1, 2, 3];
    // .find() might return number | undefined
    const result = arr.find((item) => item > 2) as number;
    let a = result * 5;
    console.log(a);
  4. Implement authentication guards using Next.js Middleware

    Create a middleware.js file in the root directory (at the same level as the app directory) to intercept requests. This allows you to protect admin routes by checking for the existence of an authentication cookie (e.g., admin-token). If the user is not authenticated and is not accessing the login page, they are redirected to the login page.
    import { NextResponse } from 'next/server'
     
    export function middleware(request) {
        if(request.nextUrl.pathname.startsWith('/admin')){
            // Allow access to the login page
            if(!request.nextUrl.pathname.startsWith('/admin/login')){
                // Check for authentication cookie
                if(request.cookies.get('admin-token')){
                    // Authenticated, proceed
                } else {
                    // Not authenticated, redirect to login
                    return NextResponse.redirect(new URL('/admin/login', request.url))
                }
            }
        }
    }
  5. Implement loading states with Suspense

    To show a loading state during asynchronous data fetching, wrap the page content or the layout children in a <Suspense> component and provide a fallback UI (e.g., a loading.js component). The loading state will only trigger if the rendered page contains an asynchronous operation (like async/await).
    import { Suspense } from "react";
    import Loading from './loading';
    
    export default function RootLayout({ children }) {
      return (
        <html>
          <body>
            <Suspense fallback={<Loading />}>
              {children}
            </Suspense>
          </body>
        </html>
      );
    }
    
    // Example of an async page that triggers the loading state
    export default async function Posts() {
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return <div>Content Loaded</div>;
    }
  6. Implement auto-scrolling live comments in uni-app

    To create a live comment effect where the list automatically scrolls up as new items are added, use the scroll-view component with scroll-y enabled. The key is to dynamically update the scroll-into-view property to the ID of the most recently added element.

    Key Implementation Details:

    • scroll-into-view: Must be bound to a variable that matches the id of the target element (e.g., gift0, gift1).
    • scroll-with-animation: Enable this to ensure smooth scrolling transitions.
    • Initial State: The data list should have an initial value (e.g., ['']) to ensure the scrolling mechanism initializes correctly.
    • ID Management: Use a numeric index to generate unique IDs for each list item. Avoid using dataList.length - 1 directly inside the template string for scroll-into-view as it may not trigger the scroll; instead, assign the calculated index to a dedicated variable.
    <template>
      <scroll-view class="ul" scroll-y :scroll-into-view="lastItemView" scroll-with-animation>
        <view v-for="(item, idx) in dataList" :key="idx" :id="'gift' + idx">
          <view class="li" v-if="item != ''">
            <text>{{ item }}</text>
          </view>
        </view>
      </scroll-view>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue';
    
    const lastItemView = ref(null);
    const scrollTimeTask = ref(null);
    const dataList = ref(['']);
    
    const autoScrollset = () => {
      let idx = 0;
      if (scrollTimeTask.value) clearInterval(scrollTimeTask.value);
      
      scrollTimeTask.value = setInterval(() => {
        let target = 'Comment ' + idx;
        lastItemView.value = `gift${idx}`;
        dataList.value.push(target);
        idx++;
      }, 500);
    };
    
    onMounted(() => {
      autoScrollset();
    });
    </script>
    
    <style scoped>
    .ul { height: 500px; }
    </style>
  7. Implement a filtered and paginated data table for a Vue 3 / Element Plus dropdown

    This implementation demonstrates how to manage a complex dataset within a custom dropdown popover. It includes multi-level filtering (category and sub-category), keyword search, and client-side pagination. The logic uses Vue 3 computed properties to derive the filtered list and a reload function to slice that list for the current page of the table.
    import { ref, computed, nextTick } from 'vue'
    
    const pageSize = 5
    const currentPage = ref(1)
    const firstValue = ref('') // Category filter
    const secondValue = ref('') // Sub-category filter
    const keyword = ref('') // Search keyword
    
    // Filter logic
    const filterList = computed(() => {
      let arr = allList
      if (firstValue.value) arr = arr.filter(v => v.main === firstValue.value)
      if (secondValue.value) arr = arr.filter(v => v.sub === secondValue.value)
      if (keyword.value) {
        const k = keyword.value.trim().toLowerCase()
        arr = arr.filter(v => v.id.toLowerCase().includes(k) || v.name.toLowerCase().includes(k))
      }
      return arr
    })
    
    const tableData = ref([])
    const reload = () => {
      const start = (currentPage.value - 1) * pageSize
      const end = start + pageSize
      tableData.value = filterList.value.slice(start, end)
    }
    
    const doSearch = () => {
      currentPage.value = 1
      reload()
    }
    
    const doReset = () => {
      firstValue.value = ''
      secondValue.value = ''
      keyword.value = ''
      currentPage.value = 1
      reload()
    }
  8. Develop WeChat Mini Programs using VS Code and WeChat DevTools

    You can use VS Code as your primary editor for coding while keeping the WeChat DevTools open for debugging. Because the WeChat DevTools automatically compile files saved in VS Code, you can benefit from VS Code's superior syntax highlighting, code hints, and formatting while maintaining the official debugging environment.
  9. Perform basic database queries and chain operations in ThinkPHP

    ThinkPHP uses a chainable query builder. Methods like table() and where() return a Query object, allowing multiple calls in a sequence. The query is executed and returns a result set (array or dataset) only when a terminal method like select() or find() is called. Use toArray() to convert the result set into a standard PHP array.
    // Basic select and debug output
    $user = Db::table("user")->select();
    halt($user);
    
    // Convert result to array
    $user = Db::table("user")->select()->toArray();
    halt($user);
    
    // Chain query with where clause
    $user = Db::table("user")->where("age", 14)->select();