質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.92%

GoogleドキュメントへGASのサイドバーから装飾済みの本文などをカーソル位置へ挿入したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 181

kikuchi33

score 9

やりたいこと

Googleドキュメントを使っています。

GASで作ったサイドバーに設置した画像やテキストのリンクをクリックすることで

●装飾設定もした見出し1&本文のセット
●装飾設定もした表(表内に本文などの値も含む)
●指定した描画ファイル

これらの予めセットしておいた情報を
カーソルの置かれている場所へ挿入したいと考えています。

試したこと

https://tonari-it.com/gas-sidebar-document-cursor-insert/
こちらを元にGASで作ったサイドバーのテキストフィールドに
入力したテキスト情報をカーソル位置に挿入はできました。

知りたいこと

1.予め登録しておいた装飾済みの見出し・本文セットや表等の情報をGASサイドバーからカーソル位置へ挿入する方法
2.そもそも上記が技術的に可能なのかどうか

GASを使ったサイドバーの情報が
とにかく日本語に少なくてこれ以上自分で調べられないため
質問をさせていただきました。

ご存じの方がいらっしゃればお知恵をお貸しいただければ幸いです。
よろしくお願いいたします。

追伸 2020/07/19 23:16

関連する単語で検索を進めております。
英語が読めないので自動翻訳頼りですが、
https://stackoverrun.com/ja/q/11664878
こちらがなんとなく関係しそうかなと読み進めて、
https://developers.google.com/apps-script/guides/docs#structure_of_a_document
こちらのページに辿り着きましたが、やはり理解はできませんでした。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • draq

    2020/07/20 15:11

    サイドバーからドキュメンを操作する処理を起動する部分はわかるということでいいんでしょうか。
    あとドキュメントのデータ構造についてはどこまでご存知ですか?

    キャンセル

  • kikuchi33

    2020/07/21 09:13

    お返事ありがとうございます。

    https://tonari-it.com/gas-sidebar-document-cursor-insert/
    こちらで紹介されているコードをテストファイルへ追加し
    上記記事の動作を確認した程度で、内容まで理解ができておりません。

    そのためサイドバーからドキュメントを操作する処理を起動する部分や
    ドキュメントデータ構造については全く把握していない状況です。

    キャンセル

  • draq

    2020/07/21 12:13 編集

    そこが理解できてないのにそれ以上の機能の実装方法を質問するのは、丸投げ(改造依頼)ではないでしょうか。
    リッチテキストとして挿入する場合は、挿入先の要素別に処理を分岐させたりする必要があるので、たとえ回答したとしても理解できるとは思えません。実装も動作確認もかなり面倒ですし。
    まずはリンク先の内容を読んで、理解できないことに絞って質問された方がいいと思います。

    > 2.そもそも上記が技術的に可能なのかどうか
    可能です。細かい組み合わせを無視しましたが一応簡単な実装はして確認しました。

    キャンセル

回答 1

checkベストアンサー

0

コメントでは厳し目なこと書きましたが、一応確認で書いた中途半端なコードは書いておきます。
メニューに「マクロ」が追加されるので、実行するとサイドバーが表示されます。

事前準備:

  • Google ドライブに挿入するテンプレートを保存するフォルダを作成する。
  • 上記フォルダにテンプレートとなるドキュメントを保存する。

制限事項:

  • Paragraph の中にカーソルがある場合は、正常に動作しません。
  • テキスト、テーブル、インラインイメージしか考慮してません。
const ui = DocumentApp.getUi();

function onOpen() {
  ui.createMenu("マクロ").addItem("showSidebar", "showSidebar").addToUi();
}

function showSidebar() {
  const files = (() => {
    const templateFolder = DriveApp.getFolderById(
      "(テンプレートとなるドキュメントファイルを保存するフォルダID)"
    );

    const iterator = templateFolder.getFilesByType(
      "application/vnd.google-apps.document"
    );
    const result = [];
    while (iterator.hasNext()) {
      const file = iterator.next();
      result.push({ id: file.getId(), name: file.getName() });
    }
    return result;
  })();

  const template = HtmlService.createTemplateFromFile("Sidebar");
  template.files = JSON.stringify(files);
  const htmlOutput = template.evaluate();
  ui.showSidebar(htmlOutput);
}

function insertDocument(fileId) {
  try {
    const insertBody = DocumentApp.openById(fileId).getBody();

    const doc = DocumentApp.getActiveDocument();
    const position = doc.getCursor();
    const element = position.getElement();
    const parent = element.getParent();
    let index = parent.getChildIndex(element);

    const parentType = parent.getType();
    const offset = position.getOffset();
    const surroundingText = position.getSurroundingText().getText();
    const surroundingTextOffset = position.getSurroundingTextOffset();


    if (parent.getType() === DocumentApp.ElementType.PARAGRAPH) {
      const parentParagraph = parent.asParagraph();
      for (let i = 0; i < insertBody.getNumChildren(); i++) {
        const child = insertBody.getChild(i);
        if (child.getType() === DocumentApp.ElementType.PARAGRAPH) {
          const paragraph = child.asParagraph().copy();
          if (paragraph.getNumChildren() !== 0)
            parentParagraph.insertParagraph(index++, paragraph);
        } else if (child.getType() === DocumentApp.ElementType.INLINE_IMAGE) {
          parentParagraph.insertInlineImage(
            index++,
            child.asInlineImage().copy()
          );
        } else if (child.getType() === DocumentApp.ElementType.TABLE) {
          parentParagraph.insertTable(index++, child.asTable().copy());
        }
      }
    } else {
      for (let i = 0; i < insertBody.getNumChildren(); i++) {
        const child = insertBody.getChild(i).copy();
        if (child.getType() === DocumentApp.ElementType.PARAGRAPH) {
          const paragraph = child.asParagraph();
          if (paragraph.getNumChildren() !== 0)
            parent.insertParagraph(index++, paragraph);
        } else if (child.getType() === DocumentApp.ElementType.INLINE_IMAGE) {
          parent.insertInlineImage(index++, child.asInlineImage().copy());
        } else if (child.getType() === DocumentApp.ElementType.TABLE) {
          parent.insertTable(index++, child.asTable().copy());
        }
      }
    }

    step = "complete";
  } catch (ex) {
    ui.alert("error = " + JSON.stringify(ex, null, 2));
  }
}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title>Sidebar</title>
    <style>
      body { text-align:center; }
      button[type=button] {
        width: 250px;
        height: 50px;
        margin-bottom: 10px;
      }
    </style>
    <script>
      window.addEventListener("DOMContentLoaded", () => {
        const body = document.querySelector("body");
        const files = JSON.parse("<?=files?>");
        files.forEach(file => {
          const button = document.createElement("button");
          button.setAttribute("type", "button");
          button.textContent = file.name;
          button.value = file.id;
          button.addEventListener("click", () =>{
            google.script.run
              .withSuccessHandler(() => {})
              .withFailureHandler(error => console.log(error))
              .insertDocument(file.id);
          } );
          body.appendChild(button);
        });
      });
    </script>
  </head>
  <body>
    <h3>テンプレート挿入</h3>
  </body>
</html>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/21 18:15

    お忙しいところご回答いただき、
    誠にありがとうございました。

    日本語で検索しても、
    装飾込みのテキストや表などの
    サイドバーからワンクリックで挿入することが
    そもそも技術的に可能かどうかすら分からなかったため、
    こちらに質問を投稿させていただきました。

    日頃から実装させたいことを検索して
    出てきたサンプルコードを一部編集したりカスタマイズするなどしているため
    おっしゃる通りまだまだ技術がなく、
    このような質問の仕方しかできず、大変申し訳ございませんでした。

    回答いただいたコードを使わせていただきました。

    まさにイメージ通りの動きをしております。

    技術的に可能なのですね。

    質問の仕方も悪かったのにも関わらず
    お忙しいところ誠にありがとうございました。

    技術的に可能ということを教えていただきましたので
    投稿いただいたコードを元にどのような処理をしているかを勉強させていただきます。

    誠にありがとうございました。

    キャンセル

  • 2020/07/22 03:25 編集

    イメージ通りとのことですが、回答にも書いたように実装は中途半端です。
    1行文字列の中にカーソルがある場合はエラーになります。未確認ですが、リストに対しても対応してませんので利用には注意してください。
    完璧に実装する場合は、(カーソル位置にあり得るオブジェクトの種類数)×(テンプレートに存在しうるオブジェクトの種類数)の組み合わせの実装が必要になるため、割と大変です。
    回答のコードでは、カーソル位置にあり得るオブジェクトを Paragraph とそれ以外しか分岐してませんが、本当にそれで十分かもよく検討が必要です。

    キャンセル

  • 2020/07/22 09:24

    ありがとうございます。
    はい、段落内にカーソルがある場合などいくつかのケースでエラーが出ておりました。

    段落内にカーソルがある場合は、カーソルのある段落の終了箇所(次の段落との間)に
    挿入ができないかと考えております。

    仕様上、技術的に可能であることがわかっただけでとてもありがたいです。
    可能性が見えました。

    誠にありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る