Skip to content

Commit e771212

Browse files
committed
Merge mobile add-on
2 parents 1e4d064 + b188e9e commit e771212

File tree

24 files changed

+1711
-36
lines changed

24 files changed

+1711
-36
lines changed

LICENSE

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
Apache License
23
Version 2.0, January 2004
34
http://www.apache.org/licenses/
@@ -186,7 +187,7 @@
186187
same "printed page" as the copyright notice for easier
187188
identification within third-party archives.
188189

189-
Copyright [2018] [Google LLC.]
190+
Copyright [yyyy] [name of copyright owner]
190191

191192
Licensed under the Apache License, Version 2.0 (the "License");
192193
you may not use this file except in compliance with the License.

android/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
## [Converting Android Apps into Android Add-ons](https://developers.google.com/apps-script/add-ons/mobile/android)
44

55
This sample describes how to convert an existing Android app into an Android add-on.
6+
7+
## Mobile Doc Translate Add-on
8+
9+
A sample Google Apps Script mobile add-on for Google Docs.

android/mobile-translate/Code.gs

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
* @OnlyCurrentDoc
3+
*
4+
* The above comment directs Apps Script to limit the scope of file
5+
* access for this add-on. It specifies that this add-on will only
6+
* attempt to read or modify the files in which the add-on is used,
7+
* and not all of the user's files. The authorization request message
8+
* presented to users will reflect this limited scope.
9+
*/
10+
11+
/**
12+
* Creates a menu entry in the Google Docs UI when the document is opened.
13+
* This method is only used by the regular add-on, and is never called by
14+
* the mobile add-on version.
15+
*
16+
* @param {object} e The event parameter for a simple onOpen trigger. To
17+
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
18+
* running in, inspect e.authMode.
19+
*/
20+
function onOpen(e) {
21+
DocumentApp.getUi().createAddonMenu()
22+
.addItem('Start', 'showSidebar')
23+
.addToUi();
24+
}
25+
26+
/**
27+
* Runs when the add-on is installed.
28+
* This method is only used by the regular add-on, and is never called by
29+
* the mobile add-on version.
30+
*
31+
* @param {object} e The event parameter for a simple onInstall trigger. To
32+
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
33+
* running in, inspect e.authMode. (In practice, onInstall triggers always
34+
* run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
35+
* AuthMode.NONE.)
36+
*/
37+
function onInstall(e) {
38+
onOpen(e);
39+
}
40+
41+
/**
42+
* Opens a sidebar in the document containing the add-on's user interface.
43+
* This method is only used by the regular add-on, and is never called by
44+
* the mobile add-on version.
45+
*/
46+
function showSidebar() {
47+
var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
48+
.setTitle('Translate');
49+
DocumentApp.getUi().showSidebar(ui);
50+
}
51+
52+
/**
53+
* Gets the text the user has selected. If there is no selection,
54+
* this function displays an error message.
55+
*
56+
* @return {Array.<string>} The selected text.
57+
*/
58+
function getSelectedText() {
59+
var selection = DocumentApp.getActiveDocument().getSelection();
60+
if (selection) {
61+
var text = [];
62+
var elements = selection.getSelectedElements();
63+
for (var i = 0; i < elements.length; i++) {
64+
if (elements[i].isPartial()) {
65+
var element = elements[i].getElement().asText();
66+
var startIndex = elements[i].getStartOffset();
67+
var endIndex = elements[i].getEndOffsetInclusive();
68+
69+
text.push(element.getText().substring(startIndex, endIndex + 1));
70+
} else {
71+
var element = elements[i].getElement();
72+
// Only translate elements that can be edited as text; skip images and
73+
// other non-text elements.
74+
if (element.editAsText) {
75+
var elementText = element.asText().getText();
76+
// This check is necessary to exclude images, which return a blank
77+
// text element.
78+
if (elementText != '') {
79+
text.push(elementText);
80+
}
81+
}
82+
}
83+
}
84+
if (text.length == 0) {
85+
throw 'Please select some text.';
86+
}
87+
return text;
88+
} else {
89+
throw 'Please select some text.';
90+
}
91+
}
92+
93+
/**
94+
* Gets the stored user preferences for the origin and destination languages,
95+
* if they exist.
96+
* This method is only used by the regular add-on, and is never called by
97+
* the mobile add-on version.
98+
*
99+
* @return {Object} The user's origin and destination language preferences, if
100+
* they exist.
101+
*/
102+
function getPreferences() {
103+
var userProperties = PropertiesService.getUserProperties();
104+
var languagePrefs = {
105+
originLang: userProperties.getProperty('originLang'),
106+
destLang: userProperties.getProperty('destLang')
107+
};
108+
return languagePrefs;
109+
}
110+
111+
/**
112+
* Gets the user-selected text and translates it from the origin language to the
113+
* destination language. The languages are notated by their two-letter short
114+
* form. For example, English is 'en', and Spanish is 'es'. The origin language
115+
* may be specified as an empty string to indicate that Google Translate should
116+
* auto-detect the language.
117+
*
118+
* @param {string} origin The two-letter short form for the origin language.
119+
* @param {string} dest The two-letter short form for the destination language.
120+
* @param {boolean} savePrefs Whether to save the origin and destination
121+
* language preferences.
122+
* @return {Object} Object containing the original text and the result of the
123+
* translation.
124+
*/
125+
function getTextAndTranslation(origin, dest, savePrefs) {
126+
var result = {};
127+
var text = getSelectedText();
128+
result['text'] = text.join('\n');
129+
130+
if (savePrefs == true) {
131+
var userProperties = PropertiesService.getUserProperties();
132+
userProperties.setProperty('originLang', origin);
133+
userProperties.setProperty('destLang', dest);
134+
}
135+
136+
result['translation'] = translateText(result['text'], origin, dest);
137+
138+
return result;
139+
}
140+
141+
/**
142+
* Replaces the text of the current selection with the provided text, or
143+
* inserts text at the current cursor location. (There will always be either
144+
* a selection or a cursor.) If multiple elements are selected, only inserts the
145+
* translated text in the first element that can contain text and removes the
146+
* other elements.
147+
*
148+
* @param {string} newText The text with which to replace the current selection.
149+
*/
150+
function insertText(newText) {
151+
var selection = DocumentApp.getActiveDocument().getSelection();
152+
if (selection) {
153+
var replaced = false;
154+
var elements = selection.getSelectedElements();
155+
if (elements.length == 1 &&
156+
elements[0].getElement().getType() ==
157+
DocumentApp.ElementType.INLINE_IMAGE) {
158+
throw "Can't insert text into an image.";
159+
}
160+
for (var i = 0; i < elements.length; i++) {
161+
if (elements[i].isPartial()) {
162+
var element = elements[i].getElement().asText();
163+
var startIndex = elements[i].getStartOffset();
164+
var endIndex = elements[i].getEndOffsetInclusive();
165+
166+
var remainingText = element.getText().substring(endIndex + 1);
167+
element.deleteText(startIndex, endIndex);
168+
if (!replaced) {
169+
element.insertText(startIndex, newText);
170+
replaced = true;
171+
} else {
172+
// This block handles a selection that ends with a partial element. We
173+
// want to copy this partial text to the previous element so we don't
174+
// have a line-break before the last partial.
175+
var parent = element.getParent();
176+
parent.getPreviousSibling().asText().appendText(remainingText);
177+
// We cannot remove the last paragraph of a doc. If this is the case,
178+
// just remove the text within the last paragraph instead.
179+
if (parent.getNextSibling()) {
180+
parent.removeFromParent();
181+
} else {
182+
element.removeFromParent();
183+
}
184+
}
185+
} else {
186+
var element = elements[i].getElement();
187+
if (!replaced && element.editAsText) {
188+
// Only translate elements that can be edited as text, removing other
189+
// elements.
190+
element.clear();
191+
element.asText().setText(newText);
192+
replaced = true;
193+
} else {
194+
// We cannot remove the last paragraph of a doc. If this is the case,
195+
// just clear the element.
196+
if (element.getNextSibling()) {
197+
element.removeFromParent();
198+
} else {
199+
element.clear();
200+
}
201+
}
202+
}
203+
}
204+
} else {
205+
var cursor = DocumentApp.getActiveDocument().getCursor();
206+
var surroundingText = cursor.getSurroundingText().getText();
207+
var surroundingTextOffset = cursor.getSurroundingTextOffset();
208+
209+
// If the cursor follows or preceds a non-space character, insert a space
210+
// between the character and the translation. Otherwise, just insert the
211+
// translation.
212+
if (surroundingTextOffset > 0) {
213+
if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') {
214+
newText = ' ' + newText;
215+
}
216+
}
217+
if (surroundingTextOffset < surroundingText.length) {
218+
if (surroundingText.charAt(surroundingTextOffset) != ' ') {
219+
newText += ' ';
220+
}
221+
}
222+
cursor.insertText(newText);
223+
}
224+
}
225+
226+
227+
/**
228+
* Given text, translate it from the origin language to the destination
229+
* language. The languages are notated by their two-letter short form. For
230+
* example, English is 'en', and Spanish is 'es'. The origin language may be
231+
* specified as an empty string to indicate that Google Translate should
232+
* auto-detect the language.
233+
*
234+
* @param {string} text text to translate.
235+
* @param {string} origin The two-letter short form for the origin language.
236+
* @param {string} dest The two-letter short form for the destination language.
237+
* @return {string} The result of the translation, or the original text if
238+
* origin and dest languages are the same.
239+
*/
240+
function translateText(text, origin, dest) {
241+
if (origin === dest) {
242+
return text;
243+
}
244+
return LanguageApp.translate(text, origin, dest);
245+
}

android/mobile-translate/README.md

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
Mobile Doc Translate Add-on
2+
===========================
3+
4+
A sample Google Apps Script mobile add-on for Google Docs. This add-on is
5+
essentially a mobile version of the Docs
6+
[Translate Add-on Quickstart](https://developers.google.com/apps-script/quickstart/docs).
7+
8+
Introduction
9+
------------
10+
11+
Google Apps Script now allows developers to construct Mobile Add-ons -- Android
12+
applications which extend and support Google Docs and Sheets.
13+
14+
This sample shows how to construct a mobile add-on called
15+
**Mobile Doc Translate**. This add-on allows users to select text in a
16+
Google Doc on their mobile device and see a translation of that text in one
17+
of several languages. The user can then edit the translation as needed and
18+
replace the original selected text in the Doc with the translation.
19+
20+
21+
Getting Started
22+
---------------
23+
24+
The add-on will need to call an Apps Script project to get Doc text, make
25+
translations, and insert text into the Doc. Users can access this add-on from
26+
the Google Docs Android app by highlighting text and selecting the add-on in the
27+
text context menu.
28+
29+
The Apps Script code file for this project is `Code.gs`. This is the same code
30+
used in the [Translate Add-on Quickstart](https://developers.google.com/apps-script/quickstart/docs),
31+
but does not include the HTML code that defines the quickstart's sidebar.
32+
33+
The mobile add-on will make use of the
34+
[Apps Script Execution API](https://developers.google.com/apps-script/guides/rest/)
35+
to call the `Code.gs` functions. The
36+
[Execution API quickstart for Android](https://developers.google.com/apps-script/guides/rest/quickstart/android)
37+
describes how to call Apps Script functions from Android applications.
38+
39+
To build this sample:
40+
41+
1. The `app/` folder in this repository contains all the required Android files
42+
for this add-on. These can be manually copied or imported into a new Android
43+
Studio project.
44+
1. Create a new Apps Script project.
45+
1. Replace the code in the new project's `Code.gs` file with the code from this
46+
repo.
47+
1. Save the project.
48+
1. In the code editor, select **Publish > Deploy as API** executable.
49+
1. In the dialog that opens, leave the **Version** as "New" and enter
50+
"Target-v1" into the text box. Click **Deploy**.
51+
1. Follow the
52+
[Keytool SHA1 Fingerprint](https://developers.google.com/apps-script/guides/rest/quickstart/android#step_1_acquire_a_sha1_fingerprint)
53+
instructions to acquire a SHA1 fingerprint for your project.
54+
1. Using that SHA code, follow the
55+
[Turn on the Execution API](https://developers.google.com/apps-script/guides/rest/quickstart/android#step_2_turn_on_the_api_name)
56+
instructions to enable the API for your script project and create OAuth
57+
credentials. Be sure to match the same package name used in your Android
58+
code.
59+
1. Edit the `MainActivity.java` file so that the `SCRIPT_ID` constant is set to
60+
your Apps Script project ID (in the script editor, select
61+
**File > Project properties**, and use the **Project key**).
62+
63+
These steps should allow you to build the Android app and have it successfully
64+
call the Apps Script code. You can test it by:
65+
66+
1. Install the app on a test Android device.
67+
1. Set the app as the debug app on the device by running this
68+
[ADB](https://developer.android.com/studio/command-line/adb.html)
69+
command:
70+
`$ adb shell am set-debug-app --persistent <YOUR_PACKAGE_NAME>`
71+
1. Open a docucment using the Google Docs app on the device.
72+
1. Highlight some text in the doc and select the three-dot icon to open the
73+
context menu, and then select **Mobile Doc Translate**.
74+
75+
Learn more
76+
----------
77+
78+
To continue learning about mobile add-ons for Google Docs and Sheets,
79+
take a look at the following resources:
80+
81+
* [Mobile Add-ons](https://developers.google.com/apps-script/add-ons/mobile)
82+
* [Apps Script Execution API](https://developers.google.com/apps-script/guides/)
83+
84+
Support
85+
-------
86+
87+
For general Apps Script support, check the following:
88+
89+
- Stack Overflow Tag: [google-apps-script](http://stackoverflow.com/questions/tagged/google-apps-script)
90+
- Issue Tracker: [google-apps-script-issues](https://code.google.com/p/google-apps-script-issues/issues/list)
91+
92+
If you've found an error in this sample, please file an issue:
93+
https://github.com/googlesamples/apps-script-mobile-addons
94+
95+
Patches are encouraged, and may be submitted by forking this project and
96+
submitting a pull request through GitHub.
97+
98+
License
99+
-------
100+
101+
Copyright 2016 Google, Inc.
102+
103+
Licensed to the Apache Software Foundation (ASF) under one
104+
or more contributor license agreements. See the NOTICE file
105+
distributed with this work for additional information
106+
regarding copyright ownership. The ASF licenses this file
107+
to you under the Apache License, Version 2.0 (the
108+
"License"); you may not use this file except in compliance
109+
with the License. You may obtain a copy of the License at
110+
111+
http://www.apache.org/licenses/LICENSE-2.0
112+
113+
Unless required by applicable law or agreed to in writing,
114+
software distributed under the License is distributed on an
115+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
116+
KIND, either express or implied. See the License for the
117+
specific language governing permissions and limitations
118+
under the License.

0 commit comments

Comments
 (0)