Apps Scriptリファレンス: Apps Script Reference |障害・課題追跡: IssueTracker |Google Workspace: Status Dashboard - Summary

2019年10月31日木曜日

ドライブのゴミ箱内のファイルを完全削除したい(Drve API)


Googleドライブの指定したフォルダ内のファイルを完全に削除して復帰できなくしたい
では指定したフォルダ内のファイルを完全に削除するコードを書きました。

ここではゴミ箱に捨てたファイルを完全に削除するコードを書きます。

deleteTrashedFiles()を実行すると復帰できないので
Drive.Files.remove(id)
はコメントアウトしています。

Drive.Files.remove(id) を実行する場合は挙動を十分理解した上で使いましょう。

もう一度書きますが、この方法で削除したファイルは二度と復帰することができません。



コード.gs
function deleteTrashedFiles() {
  var contents = DriveApp.getTrashedFiles();
  var i = 0;
  while(contents.hasNext()) {
    var file = contents.next();
    var id = file.getId();
    i++;
    Logger.log(file.getName());// 削除されるファイル
    //Drive.Files.remove(id);// この行のコメントを外して実行すると削除されます
    Utilities.sleep(1000);
  }
}


補足

手元のドライブでやった時はゴミ箱にあるファイル数が多くて途中で何度もエラーになりました。

Utilities.sleep(1000);
を入れることで一度により多くのファイルを削除できるようになりましたが、それでもエラーが出て、何度か deleteTrashedFiles() を実行してゴミ箱のファイルを完全削除できました。


関連記事

Googleドライブのファイルを完全に削除したい(Drive API)
Googleドライブの指定したフォルダ内のファイルを完全に削除して復帰できなくしたい


参考

getTrashedFiles()
https://developers.google.com/apps-script/reference/drive/drive-app#getTrashedFiles()

Googleドライブの指定したフォルダ内のファイルを完全に削除して復帰できなくしたい


Googleドライブのファイルを完全に削除したい(Drive API)
ではファイル名を特定して完全削除しました

ここではファイル名を指定せずにフォルダ内のすべてのファイルを完全削除するコードを書きました。

実行したら二度と復帰できません。
なのでここではremoveのコードはコメントアウトしています。
Drive.Files.removeを利用する場合は、その結果どうなるのか十分理解した上で利用しましょう。



コード.gs
function deleteFilesInFolder() {
  var FOLDER_ID = "FOLDER_ID";
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var contents = folder.getFiles();
  var ids = [];
  while(contents.hasNext()) {
    var file = contents.next();
    var id = file.getId();
    Logger.log(file.getName());// 削除されるファイル
    //Drive.Files.remove(id);// この行のコメントを外して実行すると削除されます
  }
}


関連記事

Googleドライブのファイルを完全に削除したい(Drive API)



2019年10月28日月曜日

TextFinderで正規表現を使って検索したい


TextFinderで正規表現を使いたくて書きました。

AND検索をしたかったので

正規表現でAND検索を試してみる2 

でやった正規表現を使います。


各々設定する箇所
  • SPREADSHEET_URL
  • 文字列1
  • 文字列2

を設定してfindAllRows()を実行すると
正規表現に一致する行が取得できます。


コード.gs
function findAllRows(){
  var rows = [];
  var ss_url = 'SPREADSHEET_URL';
  var ss = SpreadsheetApp.openByUrl(ss_url);
  var sheet = ss.getSheets()[0];

  var textFinder = sheet.createTextFinder("^(?=.*文字列1)(?=.*文字列2)").useRegularExpression(true);
  var ranges = textFinder.findAll();
  for ( var i = 0; i < ranges.length; i++ ) {
      rows.push(ranges[i].getRow()) ;
  }
  Logger.log(rows);
}




補足1

複数のセルをまたいで抽出するには別途ループ処理などの対応が必要です。

たとえば

"^(?=.*あめ)(?=.*がむ)"

とした時

A1セルに「あめ、がむ」←同じセル内なのでHITする
A2セルに「あめ」B2セルに「がむ」←セルが異なるのでHITしない


補足2

セルに改行が入っている場合はこれでいけた

"^(?=.*文字列1[\\s\\S])(?=.*文字列2[\\s\\S])"


こちらを参考にさせていただきました。
https://teratail.com/questions/197111


補足3

特殊文字を含む場合はエスケープが必要

たとえば以下のようなURLを完全一致で検索する場合

var value = "https://drive.google.com/file/d/ID/view?usp=drivesdk";
var pattern = "^" + value.replace(/\//g, "\\/").replace(/\?/g, "\\?").replace(/\=/g, "\\=") + "$";
var textFinder = sheet.createTextFinder(pattern).useRegularExpression(true);

手元ではこれでいけた。


参考

useRegularExpression(useRegEx)
https://developers.google.com/apps-script/reference/spreadsheet/text-finder.html#useRegularExpression(Boolean)

https://teratail.com/questions/197111

2019年10月26日土曜日

iPhoneでiCloud写真への自動保存をオフにしたい(充電50%以上でオフにできた)


「iCloud > 写真」 の設定で

オリジナルをダウンロードにチェックを入れて「iCloud写真」をオフにしようとしたら
160項目のフル解像度の写真とビデオをダウンロードできませんでした。続行すると、これらの写真とビデオの低解像度のバージョンは削除されます。
というメッセージが出た。

ググってみると他にも困っている人がいる情報はあるが、解決策がよくわからない、、


手元の環境で確認した解決策

「iPhoneを充電して50%以上にする」

これで上記メッセージが表示されなくなり、「iCloud写真」をオフにできました。


解決策までの道

iCloudの容量の5GBがいっぱいになった。
容量を増やすにはお金がかるらしい。
そこにお金をかけたくない。
写真はこれからも撮り続けたい。
iPhoneに保存できればいい。
iCloudには保存しなくてもいい。

そこで
iPhoneでiCloud写真への自動保存をオフにしたい。
iCloudにオリジナル解像度の写真があったらiPhone側に移動しておきたい。
(そうしないとiPhone側には低解像度の写真しか残らなくなるらしい)

ググってみると
「iCloud > 写真」 の設定でできそう


「オリジナルをダウンロード」を選択して「iCloud写真」をオフにしようとしたら
160項目のフル解像度の写真とビデオをダウンロードできませんでした。続行すると、これらの写真とビデオの低解像度のバージョンは削除されます。
というメッセージが出た。

iPhone端末には40GB以上の空き容量がある。
iCloud側の容量は5GBなのでiPhone側の容量不足でダウンロードできないことはない。
ダウンロードできない160項目の内容はわからない。
このまま削除を進めると何が消えるかわからない。
こわくて消せない。

もうちょっとググってみる。

知恵袋にこのような回答がされている
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12197326957

電源にさすと行けるかも。
電池の残りを見ると40%。
しばらく充電してみる。

充電したまま50%を越えてから再度試してみる。

「オリジナルをダウンロード」を選択して「iCloud写真」をオフにできた。


2019年10月24日木曜日

GASからLambdaにzendesk_idを渡してzendesk APIのGETで情報取得したい


zendeskにIP制限がかかっているとGASからは zendesk APIにアクセスできない。
Lambdaを利用するとIP固定ができるので
GAS→Lambda→zendesk API という順番でやってみたときの備忘録です。


GAS側のコード

hostとendPointは各々の設定
  • hostは利用しているzendeskのURL
  • endPointはAPI GatewayのURL
  • zendesk_idは取得したいチケットのid

getZendeskInfo()を実行すると
  1. API Gatewayを通ってLambdaにhost, path, zendesk_id, zendesk_api_token_base64が渡される
  2. Lambda側のコードでzendeskのAPIのGETが実行される
  3. 指定したzendesk_idの情報が返ってくる
  4. GAS側のログに出力される


コード.gs
var host = 'SITENAME.zendesk.com';
var endPoint = 'https://API_ID.execute-api.us-east-1.amazonaws.com/default/FUNCTION_NAME';

function getZendeskInfo() {
  var zendesk_id = 2;
  var response = run(zendesk_id);
  Logger.log(response);
}

function run(zendesk_id) {
  var path = '/api/v2/tickets/' + zendesk_id + '.json';// zendesk_idを指定して取得したい場合
  //var path = '/api/v2/search.json?page=1&query=created%3E2019-01-01%20type:ticket'; // queryで取得したい場合
  var payload = {
  'data':{
    'host': host,
    'path': path,
    'zendesk_id': zendesk_id,
    'zendesk_api_token_base64': getProp('zendesk_api_token_base64')
   }
  }
  var options = {
    'method': 'post',
    'headers': get_headers(),
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  }
  Logger.log(options)
  var response = UrlFetchApp.fetch(endPoint, options);
  console.log(response);
  return response;
}
  
function get_headers() {
  var headers = {
    "x-api-key": getProp('x_api_key')
  }
  return headers;
}

function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}



Lambda側のコード

index.js
var https = require('https');

var zendesk_id;
var zendesk_api_token_base64;
var host;
var path;
exports.handler = async function(event) {
 console.log(event);
 
 /******
 pyloadで受け取る場合
 ******/
 var body = event['body'];
 var jobj = JSON.parse(body);
 host = jobj['data']['host']
 path = jobj['data']['path']
 zendesk_id = jobj['data']['zendesk_id']
 zendesk_api_token_base64 = jobj['data']['zendesk_api_token_base64']
 console.log([zendesk_id]);
 
 var results = await getData(zendesk_id); //getData()の処理が終わってから次の処理を実行する
 var json = returnJson(results);
 return json;
}

//Promise
function getData() {
 return new Promise(resolveFunc);
}

//Promise resolved
function resolveFunc(arg) {
 https.get(getOptions(), function(res) {
    var body = '';
    //res.setEncoding('utf8');
    res.on('data', function(chunk) {
     body += chunk;
    });
    res.on('end', function(chunk) {
     arg(body);
    });
    res.on('error', function(e) {
     console.log(e.message);
     arg(e.message);
    });
 });
 //ここまでの処理をPromiseでやりたい
}

function getOptions() {
 var options = {
    "method": 'GET',
    "host": host,
    "path": path,
    "headers": {
     "Content-type": "application/json",
     "Authorization": " Basic " + zendesk_api_token_base64// Lambda側に設定して読む場合はprocess.env. zendesk_api_token_base64
    }
 };
 return options;
}

//JSONを返す
function returnJson(results) {
 const response = {
    //body: JSON.stringify(result),
    body: results,
 };
 return response;
}




スプレッドシートのA列を編集したときに入力規則を設定したい


やりたいこと

  1. スクリプト内にデータの入力規則のリストを設定しておく
  2. A列に値を入れると、B列にデータの入力規則が追加される
  3. 列の値が空になると、B列の入力規則がクリアされる(選択肢が消える)


このように



コード.gs
var sheet = SpreadsheetApp.getActiveSheet();
var editcol = 1;// この列が編集されたら
var listcol = 2;// この列のデータの入力規則を設定する(editcolが空ならクリアする)

var dataList = [
  'TODO',
  'INPROGRESS',
  'DONE'
  ]

function onEdit() {
  var active = sheet.getActiveRange();
  var row = active.getRow();
  var range = sheet.getRange(row, listcol);
  if(active.getColumn() === editcol) {
    if(active.getValue() === '') {
      range.clearDataValidations();// データの入力規則をクリアする
    } else {
      addDataValidation(range);
    }
  }
}

//データの入力規則を設定する
function addDataValidation(range) {
  range.setDataValidation(SpreadsheetApp.newDataValidation()
  .setAllowInvalid(true)
  .requireValueInList(dataList, true)
  .build());
}



参考

Class DataValidationBuilder
https://developers.google.com/apps-script/reference/spreadsheet/data-validation-builder

GASで作ったWebアプリのアクセス権の変更はオーナーだけができる


タイトルそのままですが

スクリプトのオーナー以外が実行権限を変更しようとしても変更できない


disabledを消して無理やり選択可能にしても


おこられる。



2019年10月22日火曜日

GASで実行するユーザを個別に絞りたい


emailsで設定したユーザーだけが実行できるようにしたい


HtmlServiceのスクリプトで書く場合

ウェブアプリの導入の設定が
User accessing the web app(このアプリケーションにアクセスしているユーザー)
全ユーザーがアクセスできる状態で
var emails で設定した
  'foo@gmail.com',
  'bar@gmail.com'
だけが実行できるようにしたい。


コード.gs
var emails = [
  'foo@gmail.com',
  'bar@gmail.com'
  ];

var email = Session.getActiveUser().getEmail();
var checkEmail = emails.some(judge);

function judge(value) {
  return value === email;
}


function doGet(e) {
  if(checkEmail === true) {
    var html = '<label>hello</label>';
  } else {
    var html = '<div>このアプリを開く権限がありません。</div>';
  }
  return HtmlService.createHtmlOutput(html);
}








スプレッドシート内のスクリプトで書く場合
(ちょっと書き方を変えた)

シートを複数人で共有している状態で
  'foo@gmail.com',
  'bar@gmail.com'
だけがスクリプトを実行できるようにしたい。


コード.gs
var email = Session.getActiveUser().getEmail();

function myFunction() {
  var checkEmail = judgeEmail(email);
  var message;
  if(checkEmail === true) {
    message = 'hello';
  } else {
    message = 'このアプリを実行する権限がありません。';
  }
  Browser.msgBox(message)
}

var emails = [
  'foo@gmail.com',
  'bar@gmail.com'
  ];

function judgeEmail() {
  return emails.some(function(value) {
    return value === email;
  });
}




2019年10月21日月曜日

JavaScriptでeval()を使わずに文字列をコードとして使いたい


Googleドライブにtext/plainで保存されたコードを読み込んで動かしたい。

こういうテキストファイルがあって、JavaScriptの中でオブジェクトにして使いたい。



eval()の引数に渡せばできますが、参考サイトに「必要以上に eval を使用しないで!」と書かれているのでeval()を使わない方法で試してみます。

以下のコード2.gsで new Function() を使って引数に文字列を入れる方法でやってみました。

その前にテスト用のデータを用意しておくコードもコード1.gsで書いておきました。


フォルダIDを設定して create_file() を実行すると
作成されたファイルのURLがログに出る

コード1.gs
var FOLDER_ID = "フォルダID";

function create_file() {
  var data = '[{id: 123, message: "hello"}]'
  var content_type = "text/plain";
  var file_name = "test";
  var blob = Utilities.newBlob("", content_type, file_name);
  var file = blob.setDataFromString(data, "UTF-8");
  var folder = DriveApp.getFolderById(FOLDER_ID);
  var result = folder.createFile(file);
  Logger.log(result.getUrl())
}


コード1.gsのcreate_file()実行後のログに出たURLからIDを取得して
コード2.gsのファイルIDに設定し
myFunction() を実行する

コード2.gs
var FILE_ID = "ファイルID";

function myFunction() {
  var data = getData();
  var obj = altEval(data);
  var message = obj()["events"][0]["message"];
  Logger.log(message)
}

function getData() {
  var file = DriveApp.getFileById(FILE_ID);
  var blob = file.getBlob()
  var data = blob.getDataAsString();
  return data;
}

function altEval(data) {
  var obj = '{ events: ' + data + '}';
  var str = 'return ' + obj;
  var func = new Function(str);
  return func;
}


実行結果



補足

ちなみに非推奨のeval()でやると

コード3.gs
var FILE_ID = "ファイルID";

function myFunction() {
  var data = getData();
  var obj = eval(data);
  var message = obj[0]["message"];
  Logger.log(message);
}

function getData() {
  var file = DriveApp.getFileById(FILE_ID);
  var blob = file.getBlob()
  var data = blob.getDataAsString();
  return data;
}

eval()を使う癖がつくと良くないので使わない方がよさそう。


2019年10月19日土曜日

スプレッドシートにチェックボックスを表示したい range.insertCheckboxes()


A列のデータに対して、B列にチェックボックスを追加したい。

チェックを入れたり外したり、チェックボックスを外したり、チェックが付いているか判定したりもできるらしい。



コード.gs
function addCheckboxes() {
  targetRange().insertCheckboxes();
}

function checked() {
  targetRange().check();
}

function unchecked() {
  targetRange().uncheck();
}

function remove() {
  targetRange().removeCheckboxes();
}

function judge() {
  var result = targetRange().isChecked();
  Logger.log(result);
}

function targetRange() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var range = sheet.getRange('B2:B' + lastRow);
  return range;
}

意訳
この機能がやること
指定した範囲にチェックボックスを挿入する


この機能がやること
指定した範囲のチェックボックスにチェックを入れる


この機能がやること
指定した範囲のチェックボックスのチェックを外す


この機能がやること
指定した範囲のチェックボックスを削除する


この機能がやること
指定した範囲のチェックボックスがチェックされているか取得して
ログに出す (すべてチェックされていたらtrueでそれ以外はfalse)


この機能がやること
開いているシートを取得して
データが入っている最終行を取得して
B2からその最終行までの範囲を指定して
その範囲を返す




補足

スクリプトを使わない方法。

メニューから、挿入 > チェックボックスで追加できる。



参考

insertCheckboxes()
https://developers.google.com/apps-script/reference/spreadsheet/range#insertCheckboxes()

check()
https://developers.google.com/apps-script/reference/spreadsheet/range#check()

removeCheckboxes()
https://developers.google.com/apps-script/reference/spreadsheet/range#removeCheckboxes()

isChecked()
https://developers.google.com/apps-script/reference/spreadsheet/range#isChecked()

isChecked()
https://developers.google.com/apps-script/reference/spreadsheet/range#ischecked

チェックボックスを追加して使用する
https://support.google.com/docs/answer/7684717?co=GENIE.Platform%3DDesktop&hl=ja


JIRAのリンクされた課題をAPIで追加したい


以下のように「リンクされた課題」をAPIで設定したくて書いたコードです。





Blocksにする例

コード.gs
var ENDPOINT = "https://SITENAME.atlassian.net/rest/api/3/issueLink";

function doPost() {
  var payload = getPayload();
  var options = getOptions(payload);
  var response = UrlFetchApp.fetch(ENDPOINT, options);
  Logger.log(response.getResponseCode());
}

function getPayload(values) {
  var data = {
    "type": {
      "name": "Blocks"
    },
    "inwardIssue": {
      "key": "KT-9"
    },
    "outwardIssue": {
      "key": "KT-8"
    }
  }
  var payload = JSON.stringify(data);
  return payload;
}

function getOptions(payload) {
  var options = {
    method: "post",
    payload: payload,
    contentType: "application/json",
    headers: {"Authorization": " Basic " + getToken()}
  }
  return options;
}

function getToken() {
  return PropertiesService.getScriptProperties().getProperty("token");
}

意訳
実行するAPIのENDPOINTを指定する

この機能がやること
payloadを取得する
optionsを取得する
ENDPOINTにoptionsを渡して結果を取得する
getResponseCodeをログに出す


この機能がやること
dataオブジェクトを作って
typeは
Blocks

inwardIssueは
KT-9(KT-9のリンクされた課題にblocksでKT-8を追加する)

outwardIssueは
KT-8(KT-8のリンクされた課題にis blocked byでKT-9を追加する)


dataをJSONにして
返す


この機能がやること
optionsオブジェクトを作って
methodはpostにして
payloadには引数で渡されたpayloadを指定して
contentTypeはapplication/jsonにして
headersにはスクリプトのプロパティに入れたtokenをgetToken()で取得して

optionsを返す


この昨日がやること
スクリプトのプロパティにあるのtokenを取得して返す




JIRAで課題のリンクを追加したい


この画面から追加できました。

この画面への道も以下に書いておきます。


参考にした公式ページではこうなっていました

自分の環境で実際にやったキャプチャ
Jira設定 > 課題 > (課題機能配下の)課題のリンク





参考

課題リンクの設定
https://ja.confluence.atlassian.com/adminjiracloud/configuring-issue-linking-818578890.html

2019年10月18日金曜日

Googleドライブ内の特定ファイルをコピーして自動で開きたい


ファイルをコピーして指定フォルダに入れる
というのをワンクリックでやりたくて書きました。



コード.gsの FILE_ID と FOLDER_ID を指定して

公開 > ウェブアプリケーションとして導入して


発行されるWebアプリのURLを開くと

指定したファイルのコピーが指定したフォルダに作成されてブラウザに表示されます。



コード.gs
function doGet() {
  var link = copy_file_to_foder();
  return HtmlService.createHtmlOutput(
    '<a id="alink" href="' + link + '"></a>' + 
    '<script>document.getElementById("alink").click();</script>'
    );
}

var FILE_ID = "コピー元のファイルID";
var FOLDER_ID = "コピーしたファイルを入れたいフォルダID";
function copy_file_to_foder() {
  var file = DriveApp.getFileById(FILE_ID);
  var file_name = file.getName();
  var copy_file_name = "Copy of " + file_name;
  var folder = DriveApp.getFolderById(FOLDER_ID);
  file.makeCopy(copy_file_name, folder);
  var url = file.getUrl();
  Logger.log(url);
  return url;
}


関連記事

スプレッドシートをマイドライブにコピーする
ファイルをコピーして指定フォルダに入れる
スクリプトファイルをコピーして指定したフォルダに入れる
HTMLServiceのテンプレートファイルをワンクリックで作成したい


2019年10月16日水曜日

スプレッドシートのFILTER関数を使ってみる


試行1
「A列を対象にして、A列が空白以外」という条件でフィルタをかけてみる
=filter(A:A,A:A<>"")
=filter(A列を対象にして,A列が空白以外)


試行2
「A列を対象にして、A列がF1に一致する」という条件でフィルタをかけてみる
=filter(A:A,(A:A = F1))
=filter(A列を対象にして,(A列がF1と一致する))


試行3
「C列を対象にして、A列がF1に一致する」という条件でフィルタをかけてみる
=filter(C2:C,(A2:A = F1))
=filter(C列を対象にして,(A列がF1に一致する))

A列が2019/01/04に一致するC列の値は、2019/01/06



参考

FILTER
https://support.google.com/docs/answer/3093197?hl=ja

2019年10月13日日曜日

複数の文章を文章ごとに分けたい(正規表現)


複数の文章を各文章ごとに分けたくて書いた正規表現

var pattern = /。\n+|。|\.\n+|\. |\n+/g;


こういう複数の文章を
var value = '句点と改行で分割する。\n句点で分割する。Split by period. Split by period with newline.\n複数改行でも分割する。\n\n最後の文';

文章単位で配列に入れたい
 [句点と改行で分割する, 句点で分割する, Split by period, Split by period with newline, 複数改行でも分割する, 最後の文]



コード.gs
function myFunction() {
  var value = '句点と改行で分割する。\n句点で分割する。Split by period. Split by period with newline.\n複数改行でも分割する。\n\n最後の文';
  var pattern = /。\n+|。|\.\n+|\. |\n+/g;
  
  var sentences = value.split(pattern);
  Logger.log(sentences);
  
  for(var i = 0; i < sentences.length; i++) {
    var sentence = sentences[i];
    Logger.log(sentence);
  }
}

意訳
この機能がやること
複数の文章
句点と改行 or 句点 or ピリオドと改行 or ピリオドと半角スペース or 改行
  
valueをpatternで分割する
ログに出す
  
文章の数だけ繰り返す
文章ごとに
ログに出す





実行結果

こういうログが出ます。

[19-10-12 19:15:05:900 PDT] [句点と改行で分割する, 句点で分割する, Split by period, Split by period with newline, 複数改行でも分割する, 最後の文]
[19-10-12 19:15:05:901 PDT] 句点と改行で分割する
[19-10-12 19:15:05:901 PDT] 句点で分割する
[19-10-12 19:15:05:902 PDT] Split by period
[19-10-12 19:15:05:902 PDT] Split by period with newline
[19-10-12 19:15:05:903 PDT] 複数改行でも分割する
[19-10-12 19:15:05:903 PDT] 最後の文



2019年10月4日金曜日

Chromeを起動したときに指定したページを開きたい


いつも確認したいページがあるとき、Chromeを起動したらそのページが開く
ということをしたくて調べました。


公式ページに以下の記載がありました。

ホームページと起動ページを設定する
https://support.google.com/chrome/answer/95314?hl=ja


公式のキャプチャはなかったので自分の環境でやった手順をやりながら書き残しました。


文字だけで書くとこの順番でできる
設定 > 起動時 > 特定のページまたはページセットを開く > 新しいページを追加 > 保存


キャプチャした手順

右上の︙から「設定」を選択します


「起動時」で検索して「特定のページまたはページセットを開く」を選択します


「新しいページを追加」を選択して開きたいサイトのURLを入れて「追加」

これで次回Chromeを起動したときには設定したページが開くようになりました。


参考

ホームページと起動ページを設定する
https://support.google.com/chrome/answer/95314?hl=ja

Latest post

Googleドキュメントに見出しを追加したい

今回の例では、ドキュメントの末尾に「見出しD」 を追加します。 見出しA, B, C, Dのスタイルは、見出し3 ( HEADING3 ) に設定しています。  下記Code.gsの  GOOGLE_DOCUMENT_URL を設定して  addHeadingToEnd()  を...