diff --git a/dist/appsscript.json b/dist/appsscript.json
new file mode 100644
index 0000000..c78cfb0
--- /dev/null
+++ b/dist/appsscript.json
@@ -0,0 +1,11 @@
+{
+ "timeZone": "America/Los_Angeles",
+ "exceptionLogging": "STACKDRIVER",
+ "runtimeVersion": "V8",
+ "dependencies": {},
+ "oauthScopes": [
+ "https://www.googleapis.com/auth/drive",
+ "https://www.googleapis.com/auth/script.container.ui",
+ "https://www.googleapis.com/auth/spreadsheets.currentonly"
+ ]
+}
diff --git a/dist/code.gs b/dist/code.gs
new file mode 100644
index 0000000..ff9060f
--- /dev/null
+++ b/dist/code.gs
@@ -0,0 +1,866 @@
+/* code.gs generated by concat-gs-files workflow: gh/pffy/code-gs */
+
+/* addon.gs */
+
+// name : addon.gs
+// git : https://github.com/pffy/markdown-table
+// author : The Pffy Authors https://pffy.dev
+// license : https://unlicense.org/
+
+function onOpen() {
+ var ui = SpreadsheetApp.getUi();
+ ui.createAddonMenu()
+ .addItem('Export range to Markdown table ...',
+ 'exportActiveRange')
+ .addItem('Export all selected ranges to Markdown tables ...',
+ 'exportAllActiveRanges')
+ .addSeparator()
+ .addItem('Export entire sheet to Markdown table ...',
+ 'exportEntireSheet')
+ .addItem('Export all sheets to Markdown tables ...',
+ 'exportAllSheets')
+ .addSeparator()
+ .addItem('Export selected named ranges to Markdown tables ...',
+ 'openSelectNamedRanges')
+ .addItem('Export all named ranges to Markdown tables ...',
+ 'exportAllNamedRanges')
+ .addToUi();
+}
+
+function onInstall() {
+ onOpen();
+}
+
+
+/*----------------------------------------------------------------------------*/
+/* fn.gs */
+
+// name : fn.gs
+// git : https://github.com/pffy/markdown-table
+// author : The Pffy Authors https://pffy.dev
+// license : https://unlicense.org/
+
+function convertActiveRange() {
+
+ const sht = SpreadsheetApp.getActiveSheet();
+ const activeRange = sht.getActiveRange();
+
+ const output = cotton(activeRange);
+ opts.output = output;
+
+ return output;
+}
+
+function convertEntireSheet() {
+
+ const sht = SpreadsheetApp.getActiveSheet();
+ const dataRange = sht.getDataRange();
+
+ const output = cotton(dataRange);
+ opts.output = output;
+
+ return output;
+}
+
+function convertAllRanges() {
+
+ const crlf = '\r\n';
+
+ const arr = [];
+ const sht = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
+
+ const rngs = sht.getActiveRangeList().getRanges();
+ let i = 1;
+ rngs.forEach(function(rng) {
+ arr.push(crlf);
+ arr.push('range ' + i);
+ arr.push(cotton(rng));
+ i++;
+ });
+
+ const output = arr.join(crlf + crlf);
+ opts.output = output;
+
+ return output;
+}
+
+function convertAllSheets() {
+
+ const crlf = '\r\n';
+
+ const arr = [];
+ const shts = SpreadsheetApp.getActiveSpreadsheet().getSheets();
+
+ shts.forEach(function(sht) {
+ const dataRange = sht.getDataRange();
+ arr.push(crlf);
+ arr.push(sht.getSheetName());
+ arr.push(cotton(dataRange));
+ });
+
+ const output = arr.join(crlf + crlf);
+ opts.output = output;
+
+ return output;
+}
+
+function convertAllNamedRanges() {
+
+ const crlf = '\r\n';
+ const prefix = '### '; // for named range header above the table
+
+ const arr = [];
+ const ss = SpreadsheetApp.getActiveSpreadsheet();
+
+ const rngs = ss.getNamedRanges().sort(orderNamedRangeByAlpha);
+
+ rngs.forEach(function(rng) {
+ arr.push(crlf);
+ arr.push(prefix + rng.getName());
+ arr.push(cotton(rng.getRange()));
+ });
+
+ const output = arr.join(crlf + crlf);
+ opts.output = output;
+
+ return output;
+}
+
+function convertSelectedNamedRanges(str) {
+
+ const crlf = '\r\n';
+ const prefix = '### '; // for named range header above the table
+
+ const arr = [];
+
+ // named ranges from user selection
+ const nr = str.split(',');
+
+ const ss = SpreadsheetApp.getActiveSpreadsheet();
+
+ // filter to only process selected named ranges
+ const rngs = ss.getNamedRanges().filter(function(rng){
+ return nr.indexOf(rng.getName()) > -1;
+ }).forEach(function(rng) {
+ arr.push(crlf);
+ arr.push(prefix + rng.getName());
+ arr.push(cotton(rng.getRange()));
+ });
+
+ const output = arr.join(crlf + crlf);
+ opts.output = output;
+
+ return output;
+}
+
+function orderNamedRangeByAlpha(a,b) {
+
+ // based on solution found here:
+ // https://stackoverflow.com/a/30840201
+
+ // alphabetical order
+ if(a.getName() < b.getName()) return -1;
+ if(a.getName() > b.getName()) return 1;
+ return 0;
+}
+
+// returns Markdown syntax for column alignments
+function getAlignSyntax(str) {
+
+ switch(str.toLowerCase()) {
+ case 'left':
+ case 'general-left':
+ return ':--';
+ case 'center':
+ case 'general-center':
+ return ':--:';
+ case 'right':
+ case 'general-right':
+ return '--:';
+ default:
+ // center will be the default for now
+ return ':--:';
+ }
+}
+
+// font functions
+
+// returns true if font is monospace; false, otherwise
+function isFontMonospace(str) {
+
+ // monospace Google fonts:
+ // https://www.google.com/fonts
+ const fonts = `anonymous pro
+azeret mono
+b612 mono
+courier
+courier new
+courier prime
+cousine
+cutive mono
+dm mono
+droid sans mono
+fira code
+fira mono
+ibm plex mono
+inconsolata
+jetbrains mono
+major mono display
+monospace
+nanum gothic coding
+noto sans mono
+nova mono
+overpass mono
+oxygen mono
+pt mono
+red hat mono
+roboto mono
+share tech mono
+space mono
+source code pro
+spline sans mono
+syne mono
+ubuntu mono
+vt323
+xanh mono
+`.trim().split(/\n/);
+
+ return (fonts.indexOf('' + str.toLowerCase()) > -1);
+}
+
+// adds Markdown text formatting syntax to string
+function addTextSyntax(str, chr) {
+ return Utilities.formatString(chr + '%s' + chr, str);
+}
+
+// more general functions
+
+// saves file to Google drive, then returns URL
+function saveToDrive(str) {
+
+ // GCP implementation:
+ // Drive API must be enabled in console/gcloud
+
+ const filename = opts.filename || 'cotton-table.markdown';
+
+ const label = opts.folder || 'Cotton Markdown Tables';
+ const folder = DriveApp.getRootFolder();
+
+ if(folder.getFoldersByName(label).hasNext()) {
+ newFolder = folder.getFoldersByName(label).next();
+ } else {
+ newFolder = folder.createFolder(label);
+ }
+
+ const file = newFolder.createFile(filename,
+ str, MimeType.PLAIN_TEXT);
+
+ opts.url = file.getUrl();
+ opts.durl = file.getDownloadUrl();
+}
+
+// returns true if an object is empty; otherwise, false.
+function isObjectEmpty(obj) {
+ return obj && (Object.keys(obj).length < 1 && obj.constructor === Object);
+}
+
+function include(filename) {
+ return HtmlService.createHtmlOutputFromFile(filename).getContent();
+}
+
+function openSidebar(filename) {
+ const ui = HtmlService.createTemplateFromFile(filename)
+ .evaluate().setTitle(opts.title).setWidth(300);
+ SpreadsheetApp.getUi().showSidebar(ui);
+}
+
+function finish() {
+
+ // using solution found here:
+ // https://stackoverflow.com/a/19316873
+ Object.keys(opts).forEach(key => delete opts[key]);
+}
+
+function say(str , title) {
+ title = title || 'Hi'
+ SpreadsheetApp.getActiveSpreadsheet()
+ .toast(str, title, -1);
+}
+
+// returns true if spreadsheet has named ranges; otherwise, false.
+function hasNamedRanges() {
+ return !!SpreadsheetApp.getActiveSpreadsheet().getNamedRanges().length;
+}
+
+function addNamedRangeSelectHtml() {
+
+ const ss = SpreadsheetApp.getActiveSpreadsheet();
+
+ const arr = [];
+ arr.push('');
+
+ return arr.join('\n');
+}
+
+// cleans Markdown in each table cell
+function cleanText(str) {
+
+ // prevents "new column"
+ // all must go
+ str = str.replace(/\|/g, '|' );
+
+ // prevents list item
+ // first must go
+ str = str.replace(/^\*/, '*' );
+ str = str.replace(/^\-/, '+' );
+ str = str.replace(/^\+/, '−' );
+
+ // prevents headers
+ // first must go
+ str = str.replace(/^#/, '#' ); // hashtag
+
+ // prevents quote block
+ // first must go
+ str = str.replace(/^>/, '>' );
+
+ return str;
+}
+
+// returns true if any checkboxes detected; otherwise, false
+function isCheckbox(validation) {
+ return validation.getCriteriaType().toJSON() === 'CHECKBOX';
+}
+
+// surrounds string with matching HTML tags
+function addHtmlTagToSyntax(str, tag) {
+ return `<${tag}>${str}${tag}>`;
+}
+
+// adds hyperlink to Markdown table syntax
+function addHyperlinkToSyntax(obj) {
+ obj.title = obj.title.replace(/\"/g, '\\"');
+ return `[${obj.label}](${obj.url} "${obj.title}")`;
+}
+
+// generates YouTube thumbnail syntax
+function addYoutubeSyntax(obj) {
+ obj.title = obj.label.replace(/\"/g, '"');
+ obj.image = `https://img.youtube.com/vi/${obj.id}/mqdefault.jpg`;
+ return `[![${obj.label}](${obj.image} "${obj.title}")](${obj.url})`;
+}
+
+// generates IMG html element
+function addImageHtml(obj) {
+ obj.label = obj.label.replace(/\"/g, """);
+ return ``;
+}
+
+
+/*----------------------------------------------------------------------------*/
+/* obj.gs */
+
+// name : obj.gs
+// git : https://github.com/pffy/markdown-table
+// author : The Pffy Authors https://pffy.dev
+// license : https://unlicense.org/
+
+function cotton (range) {
+
+ if(range === undefined || isObjectEmpty(range)) {
+ const emptytable = ' \n:--:';
+ return emptytable;
+ }
+
+ const rng = range;
+ const sht = rng.getSheet();
+
+ // count rows and columns
+ const rows = rng.getNumRows();
+ const cols = rng.getNumColumns();
+
+ // todo: add cell limit in later versions
+
+ // delimiter for Markdown columns
+ const pipe = ' | ';
+
+ // column alignments
+ const aligns = rng.getHorizontalAlignments()[0]
+ .map((a) => getAlignSyntax(a)).filter(function(e,i){
+ return !isTableColumnHidden(i+1);
+ });
+
+ // syntax for column alignments (row two)
+ const rowtwo = pipe + aligns.join(pipe) + pipe;
+
+ // font typeface
+ const fontFamilies = rng.getFontFamilies();
+
+ // formulas
+ const formulas = rng.getFormulas();
+
+ // any formulas??
+ const hasFormulas = !formulas.every(function(r){ // rows
+ return true === r.every(function(c){ // columns
+ return c === '';
+ });
+ });
+
+ // rich text values
+ const richTexts = rng.getRichTextValues();
+
+ // text styles
+ const textStyles = rng.getTextStyles();
+
+ // data validation for cell input
+ const valids = rng.getDataValidations();
+
+ // any data validation, including checkboxes
+ const hasValids = !valids.every(function(r){ // rows
+ return true === r.every(function(c){ // columns
+ return c === null;
+ });
+ });
+
+ // notes on cell input
+ const notes = rng.getNotes();
+
+ let noteCount = 0;
+ let noteItem = '';
+
+ const footnotes = [];
+
+ // any notes?
+ const hasNotes = !notes.every(function(r){ // rows
+ return true === r.every(function(c){ // columns
+ return c === "";
+ });
+ });
+
+ // cell values
+ // never used
+ // const values = rng.getValues();
+
+ // locale and user-formatted strings
+ const displayValues = rng.getDisplayValues();
+
+ // blank test function
+ const isBlank = (ev) => (ev === '');
+
+ // position in range
+ let x = 0;
+ let y = 0;
+
+ let val = '';
+ let cell = {};
+
+ let arr = [];
+ let table = [];
+
+ let therow = '';
+
+ // hyperlink depot
+ const re_hyperlink = /^(\=HYPERLINK\()/i;
+ const hyperlink = {};
+
+ let hasHyperlink = false;
+ let hasHypertext = false;
+
+ // youtube depot
+ const re_yt_watch = /\<\\>/i;
+ const yt = {};
+
+ // image depot
+ const re_img_url = /^(\=IMAGE\(\"(.*)\"\))/i;
+ const re_img_ref = /^(\=IMAGE\((\w+\d+)\))/i;
+ const img = {};
+
+ // now begins the warp and weft of the exporter
+
+ for(let i = 1; i < rows + 1; i++) {
+
+ x = i - 1;
+
+ if(!hasNotes && !hasFormulas && displayValues[x].every(isBlank)) {
+
+ arr = new Array(cols).fill(' ');
+ therow = pipe + arr.join(pipe) + pipe;
+ table.push(therow.trim());
+
+ if(i < 2) {
+ table.push(rowtwo.trim());
+ }
+
+ continue;
+
+ } else {
+ arr = [];
+ }
+
+ for(let j = 1; j < cols + 1; j++) {
+
+ y = j - 1;
+
+ cell = rng.getCell(i, j);
+
+ if(isTableColumnHidden(cell.getColumn())) {
+ continue;
+ }
+
+ if(isTableRowHidden(cell.getRow())) {
+ continue;
+ }
+
+ // pre-value processor for =IMAGE(url)
+ if(hasFormulas && re_img_url.test(formulas[x][y])) {
+
+ img.url = formulas[x][y].match(re_img_url)[2];
+ img.label = 'Image';
+
+ if(hasNotes && notes[x][y]) {
+ img.label = notes[x][y];
+ }
+
+ img.h = sht.getRowHeight(cell.getRow());
+ img.w = sht.getColumnWidth(cell.getColumn());
+
+ val = addImageHtml(img);
+
+ footnotes.push(`IMAGE: ${notes[x][y]}
${img.url}
`);
+
+ // done with the column
+ arr.push(val);
+ continue;
+ }
+
+ // pre-value processor for =IMAGE(A1)
+ if(hasFormulas && re_img_ref.test(formulas[x][y])) {
+
+ img.ref = formulas[x][y].match(re_img_ref)[2];
+ img.url = sht.getRange(img.ref).getDisplayValue();
+ img.label = 'Image';
+
+ if(hasNotes && notes[x][y]) {
+ img.label = notes[x][y];
+ }
+
+ img.h = sht.getRowHeight(cell.getRow());
+ img.w = sht.getColumnWidth(cell.getColumn());
+
+ val = addImageHtml(img);
+
+ footnotes.push(`IMAGE: ${notes[x][y]}
${img.url}
`);
+
+ // done with the column
+ arr.push(val);
+ continue;
+ }
+
+ val = displayValues[x][y].trim();
+
+ if(!val) {
+ arr.push(' ');
+ continue;
+ }
+
+ // post-value processor for YouTube thumbnail generator
+ if(re_yt_watch.test(val)) {
+
+ yt.id = val.match(re_yt_watch)[1];
+ yt.url = `https://www.youtube.com/watch?=${yt.id}`;
+
+ yt.label = 'YouTube Video';
+
+ if(hasNotes && notes[x][y]) {
+ yt.label = notes[x][y];
+ }
+
+ val = addYoutubeSyntax(yt);
+
+ footnotes.push(`VIDEO: ${notes[x][y]}
${yt.url}
`);
+
+ // done with the column
+ arr.push(val);
+ continue;
+ }
+
+
+
+ // sanitize Markdown table text
+ val = cleanText(val);
+
+ // process notes first
+ if(hasNotes && notes[x][y]) {
+ noteCount++;
+ noteItem = `${noteCount}`;
+ footnotes.push(`${noteItem} ${notes[x][y]}
`);
+ }
+
+ // simply prints text check box for this cell
+ if(hasValids && valids[x][y] && isCheckbox(valids[x][y])) {
+ if(cell.isChecked()) {
+ val = '`[X]`';
+ arr.push();
+ } else {
+ val = '`[ ]`';
+ }
+
+ // postfix the reference to the value
+ if(hasNotes && notes[x][y]) {
+ val = val + noteItem;
+ }
+
+ // checkbox cell complete; adds column to row
+ arr.push(val);
+ continue;
+ }
+
+ if(isFontMonospace(fontFamilies[x][y])) {
+ val = val.replace(/\n/g, '`
`'); // pre-processing
+ val = addTextSyntax(val, '`');
+ }
+
+ // detect and convert bold
+ if(textStyles[x][y].isBold()) {
+ val = addTextSyntax(val, '**');
+ }
+
+ // detect and convert italic
+ if(textStyles[x][y].isItalic()) {
+ val = addTextSyntax(val, '*');
+ }
+
+ // detect and convert strike-through
+ if(textStyles[x][y].isStrikethrough()) {
+ val = addTextSyntax(val, '~~');
+ }
+
+ // detect and convert underline
+ if(textStyles[x][y].isUnderline() && !richTexts[x][y].getLinkUrl()) {
+ say('hi underline!');
+ val = addHtmlTagToSyntax(val, 'ins');
+ }
+
+ // converts new line into line break HTML tag
+ // NOTE: should be after font styling
+ val = val.replace(/\n/g, '
');
+
+ // checks for =HYPERLINK()
+ hasHyperlink = hasFormulas && formulas[x][y] && re_hyperlink.test(formulas[x][y]);
+
+ // checks for hypertext formatting
+ hasHypertext = richTexts[x][y].getLinkUrl();
+
+ // if hyperlink detected, val becomes a label
+ if(hasHyperlink || hasHypertext) {
+
+ hyperlink.url = richTexts[x][y].getLinkUrl();
+ hyperlink.title = richTexts[x][y].getText();
+ hyperlink.label = val;
+
+ val = addHyperlinkToSyntax(hyperlink);
+ }
+
+ // postfix the reference to the cell value
+ if(hasNotes && notes[x][y]) {
+ val = val + noteItem;
+ }
+
+ // adds new column to current row
+ arr.push(val);
+ }
+
+ // adds new row to table
+ therow = pipe + arr.join(pipe) + pipe;
+ table.push(therow.trim());
+
+ // adds alignments row to Markdown table
+ if(i < 2) {
+ table.push(rowtwo.trim());
+ }
+ }
+
+ // add footnotes below markdown table
+ if(!!footnotes.length) {
+ table.push('\n\n### Notes\n' + footnotes.join('\n') + '\n');
+ }
+
+ // some inner functions are bleow
+
+ // returns true if row is hidden; otherwise, false.
+ function isTableRowHidden(num) {
+
+ if(sht.isRowHiddenByFilter(num)) {
+ return true;
+ }
+
+ if(sht.isRowHiddenByUser(num)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ // returns true if column is hidden; otherwise, false.
+ function isTableColumnHidden(num) {
+
+ if(sht.isColumnHiddenByUser(num)){
+ return true;
+ }
+
+ return false;
+ }
+
+ // returns Markdown table
+ return table.join('\n');
+}
+
+
+/*----------------------------------------------------------------------------*/
+/* opts.gs */
+
+// name : opts.gs
+// git : https://github.com/pffy/markdown-table
+// author : The Pffy Authors https://pffy.dev
+// license : https://unlicense.org/
+
+const opts = {};
+
+
+/*----------------------------------------------------------------------------*/
+/* ui.gs */
+
+// name : ui.gs
+// git : https://github.com/pffy/markdown-table
+// author : The Pffy Authors https://pffy.dev
+// license : https://unlicense.org/
+
+// exports current selectino as a table
+function exportActiveRange() {
+
+ opts.mode = 'markdown table';
+ opts.abbr = 'a human-readable table rendered by platforms';
+ opts.details = 'The one table you selected is in this document.';
+ opts.title = 'Cotton Markdown Tables';
+ opts.folder = opts.title;
+ opts.filename = 'cotton-table.markdown';
+
+ saveToDrive(convertActiveRange());
+ openSidebar('done');
+ finish();
+}
+
+// exports the multi-selection as tables
+function exportAllActiveRanges() {
+
+ opts.mode = 'markdown table group';
+ opts.abbr = 'many tables in one document';
+ opts.details = 'Each table represents a selection.';
+ opts.title = 'Cotton Markdown Tables';
+ opts.folder = opts.title;
+ opts.filename = 'cotton-table.markdown';
+
+ saveToDrive(convertAllRanges());
+ openSidebar('done');
+ finish();
+}
+
+// exports selected named ranges as tables
+function exportSelectedNamedRanges(str){
+
+ if(!hasNamedRanges()) {
+ sayNoNamedRanges()
+ return false;
+ }
+
+ // NOTE: not accessed from menu, accessed from sidebar
+
+ opts.mode = 'markdown table group';
+ opts.abbr = 'many tables in one document';
+ opts.details = 'Each table represents a named range.';
+ opts.title = 'Cotton Markdown Tables';
+ opts.folder = opts.title;
+ opts.filename = 'cotton-table.markdown';
+
+ saveToDrive(convertSelectedNamedRanges(str));
+ openSidebar('done');
+ finish();
+}
+
+
+// exports all the named ranges as tables
+function exportAllNamedRanges() {
+
+ if(!hasNamedRanges()) {
+ sayNoNamedRanges()
+ return false;
+ }
+
+ opts.mode = 'markdown table group';
+ opts.abbr = 'many tables in one document';
+ opts.details = 'Each table represents a named range.';
+ opts.title = 'Cotton Markdown Tables';
+ opts.folder = opts.title;
+ opts.filename = 'cotton-table.markdown';
+
+ saveToDrive(convertAllNamedRanges());
+ openSidebar('done');
+ finish();
+}
+
+// exports the active sheet in the spreadsheet
+function exportEntireSheet() {
+
+ opts.mode = 'markdown table';
+ opts.abbr = 'a human-readable table rendered by platforms';
+ opts.details = 'The entire sheet is the table in this document.';
+ opts.title = 'Cotton Markdown Tables';
+ opts.folder = opts.title;
+ opts.filename = 'cotton-table.markdown';
+
+ saveToDrive(convertEntireSheet());
+ openSidebar('done');
+ finish();
+}
+
+// exports all sheets in a spreadsheet
+function exportAllSheets() {
+
+ opts.mode = 'markdown table group';
+ opts.abbr = 'many tables in one document';
+ opts.details = 'Each table represents an entire sheet.';
+ opts.title = 'Cotton Markdown Tables';
+ opts.folder = opts.title;
+ opts.filename = 'cotton-table.markdown';
+
+ saveToDrive(convertAllSheets());
+ openSidebar('done');
+ finish();
+}
+
+// opens sidebar to select named ranges
+function openSelectNamedRanges() {
+
+ if(!hasNamedRanges()) {
+ sayNoNamedRanges()
+ return false;
+ }
+
+ opts.title = 'Cotton Markdown Tables';
+ openSidebar('select');
+}
+
+// uses toast to say something
+function sayNoNamedRanges() {
+ say('You have no named ranges in this spreadsheet. To create named ranges, try the menu item Data > "Named ranges" ...', 'Oops!');
+}
diff --git a/dist/done.html b/dist/done.html
new file mode 100644
index 0000000..5ec6b98
--- /dev/null
+++ b/dist/done.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+ Success!
+
+ You created a = opts.mode; ?> document.
+ = opts.details; ?>
+
+
+
+
+
+
+
+ != include('stylesheet'); ?>
+
+
+
+
+ != include('javascript'); ?>
+ != include('footer'); ?>
+
+
+
+
diff --git a/dist/footer.html b/dist/footer.html
new file mode 100644
index 0000000..af1a716
--- /dev/null
+++ b/dist/footer.html
@@ -0,0 +1,6 @@
+
diff --git a/dist/javascript.html b/dist/javascript.html
new file mode 100644
index 0000000..c91083b
--- /dev/null
+++ b/dist/javascript.html
@@ -0,0 +1,64 @@
+
+
+
diff --git a/dist/select.html b/dist/select.html
new file mode 100644
index 0000000..742dc63
--- /dev/null
+++ b/dist/select.html
@@ -0,0 +1,42 @@
+
+
+
+
+ = opts.title; ?>
+
+
+
+
+
+
+ Named Ranges
+
+
+ Select one or more:
+
+ != addNamedRangeSelectHtml(); ?>
+
+
+
+
+
+
+
+ != include('stylesheet'); ?>
+
+
+
+
+ != include('javascript'); ?>
+ != include('footer'); ?>
+
+
+
+
diff --git a/dist/stylesheet.html b/dist/stylesheet.html
new file mode 100644
index 0000000..08488d2
--- /dev/null
+++ b/dist/stylesheet.html
@@ -0,0 +1,57 @@
+
+
+