星期四, 3月 05, 2026

[LINQ] TakeWhile、SkipWhile

實務上遇上 TakeWhile、SkipWhile 應用情境,字串資料為 [英文 + 數字] 組合,但英文和數字字元都是隨機長度,使用 TakeWhile 來取出隨機英語字串

使用 LINQPad 來記錄
void Main()
{
  string data = "ABCDE12345".Dump("測試資料");
  (new string(data.TakeWhile(char.IsLetter).ToArray())).Dump("TakeWhile:擷取字母字元,直至字元不是字母停止");
  (new string(data.SkipWhile(char.IsLetter).ToArray())).Dump("SkipWhile:跳過字母字元,直至字元不是字母開始擷取");
}

執行結果


星期三, 3月 04, 2026

[Gmail] 應用程式密碼

之前記錄過 [SQL] Database Mail 搭配 Gmail 發信 - 應用程式密碼 設定應用程式密碼,但現在在網頁上已經找不到滑鼠點擊進入點,可以透過兩種方式進入
  • 直接輸入網址:https://myaccount.google.com/apppasswords
  • 搜尋功能:輸入 [應用程式密碼] 就會出現
最後官方文件 - 使用應用程式密碼登入帳戶 內特別提出警告,自行斟酌使用囉
Important: App passwords aren't recommended and are unnecessary in most cases. To help keep your account secure, use "Sign in with Google" to connect apps to your Google Account.

星期二, 2月 24, 2026

[Docs] Markdown 功能

Google Docs 內有 Markdown 功能可以使用,必須手動去開啟,路徑為 工具 => 偏好設定 => 啟用 Markdowm

開啟後右鍵選單上就會有 Markdown 功能

另外編輯時也支援 Markdown 語法輸入,詳見官方文章介紹 - 在 Google 文件、簡報和繪圖中使用 Markdown 

星期五, 2月 20, 2026

[GAS] 這個應用程式是由 Google Apps Script 的使用者建立

把 [網頁應用程式] 部屬出去執行後,在網頁上方匯出出現 [這個應用程式是由 Google Apps Script 的使用者建立] 安全性警告,這個沒有辦法直接移除,常見繞道隱藏方式為把網頁內嵌在 Google Site 內

直接拿 [JS] 基礎練習 UI 畫面來呈現
XFrameOptionsMode
  • ALLOWALL:No X-Frame-Options header will be set. This will let any site iframe the page, so the developer should implement their own protection against clickjacking.
  • DEFAULT:Sets the default value for the X-Frame-Options header, which preserves normal security assumptions. If a script does not set an X-Frame-Options mode, Apps Script uses this mode as the default.
function doGet() {
  return HtmlService.createTemplateFromFile('index')
    .evaluate()
    .setTitle('圖書館館藏管理系統')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
一開始嚐試把 GAS 網頁應用程式內嵌在 Google Site 時,並沒有指定 ALLOWALL,但一樣可以正常顯示,大概是 DEFAULT 會把 Google 相關服務設定為白名單或信任服務之類,所以不需要特別指定,萬一是要把 GAS 網頁應用程式內嵌在其他平台,就一定要明確設定 ALLOWALL

GAS 警語隱藏是 Goolge Site 標誌,下面則是內崁的 GAS 網頁應用程式,已經不會出現官方制式警語   

星期三, 2月 18, 2026

[JS] 基礎練習

該筆記會在 GAS 上練習 js
  • 資料來源:陣列、物件概念
  • 基礎函示:push、filter 使用
  • DOM API:使用 API 來顯示資料,而非使用 innerHTML
  • addEventListener 註冊事件:DOMContentLoaded 和 Click 事件

gs code
/**
 * 處理 HTTP GET 請求
 * @return {HtmlOutput}
 */
function doGet() {
  return HtmlService.createTemplateFromFile('index')
    .evaluate()
    .setTitle('圖書館館藏管理系統');
}

/**
 * 嵌入 HTML 檔案內容
 * @param {string} filename 檔案名稱
 * @return {string} 檔案內容
 */
function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h2>新增書籍館藏</h2>
    <div class="input-group">
      <select class="bookCategory">
        <option value="程式語言">程式語言</option>
        <option value="文學小說">文學小說</option>
        <option value="商業理財">商業理財</option>
      </select>
      <input type="text" placeholder="書籍名稱" class="bookTitle">
      <input type="text" placeholder="作者" class="bookAuthor">
      <input type="button" class="addBtn" value="加入館藏">
    </div>

    <h2>館藏查詢與篩選</h2>
    <div class="filterGroup">
      <input type="button" value="全部">
      <input type="button" value="程式語言">
      <input type="button" value="文學小說">
      <input type="button" value="商業理財">
    </div>

    <ul class="bookList"></ul>

    <?!= include('javascript'); ?>
  </body>
</html>

js.html
<script>
/**
 * 書籍測試資料
 */
let libraryData = [
  { title: "深入淺出 JavaScript", author: "Eric Freeman", category: "程式語言" },
  { title: "哈利波特", author: "J.K. Rowling", category: "文學小說" },
  { title: "原子習慣", author: "James Clear", category: "商業理財" },
  { title: "Clean Code", author: "Robert C. Martin", category: "程式語言" }
];

// 選取 DOM 元素
const bookList = document.querySelector('.bookList');
const bookTitle = document.querySelector('.bookTitle');
const bookAuthor = document.querySelector('.bookAuthor');
const bookCategory = document.querySelector('.bookCategory');
const addBtn = document.querySelector('.addBtn');
const filterGroup = document.querySelector('.filterGroup');

/**
 * 使用純 DOM API 產生書籍列表
 * @param {Array} data 欲顯示的書籍陣列
 */
function renderLibrary(data) {
  // 清空目前列表
  while (bookList.firstChild) {
    bookList.removeChild(bookList.firstChild);
  }

  // 建立新節點
  data.forEach(function(book) {
    const li = document.createElement('li');
    
    // 建立書籍資訊容器
    const infoSpan = document.createElement('span');
    infoSpan.textContent = `[${book.category}] 《${book.title}》 - 作者:${book.author}`;
    
    li.appendChild(infoSpan);
    bookList.appendChild(li);
  });
}

/**
 * 新增書籍
 */
addBtn.addEventListener('click', function() {
  const title = bookTitle.value.trim();
  const author = bookAuthor.value.trim();
  const category = bookCategory.value;

  if (title === "" || author === "") {
    alert("請完整填寫書名與作者!");
    return;
  }

  const newBook = {
    title: title,
    author: author,
    category: category
  };

  libraryData.push(newBook);
  
  // 重新渲染並清空輸入框
  renderLibrary(libraryData);
  bookTitle.value = "";
  bookAuthor.value = "";
});

/**
 * 篩選資料功能
 */
filterGroup.addEventListener('click', function(e) {
  if (e.target.type !== "button") return;

  const filterType = e.target.value;
  let result;

  if (filterType === "全部") {
    result = libraryData;
  } else {
    result = libraryData.filter(function(book) {
      return book.category === filterType;
    });
  }

  renderLibrary(result);
});

/**
 * 初始化頁面
 */
document.addEventListener('DOMContentLoaded', function() {
  renderLibrary(libraryData);
});

</script>