Compare commits
117 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04f9e5de60 | ||
|
|
5365ec3552 | ||
|
|
12a4cbb081 | ||
|
|
03dd484827 | ||
|
|
bca73403eb | ||
|
|
432db9de04 | ||
|
|
6b4268f5b0 | ||
|
|
ab229d926e | ||
|
|
1d40954594 | ||
|
|
4a4dad44af | ||
|
|
f3664a493d | ||
|
|
3f162d75dc | ||
|
|
21ccfcf766 | ||
|
|
43dd4aefda | ||
|
|
9a475b62f5 | ||
|
|
4bfc906eb9 | ||
|
|
37bd9328b8 | ||
|
|
791f523e8c | ||
|
|
70a826709f | ||
|
|
8a0d4210c6 | ||
|
|
66139e71a3 | ||
|
|
0eb26ddef4 | ||
|
|
9a75e6e92a | ||
|
|
b49b78ad88 | ||
|
|
411eb0f86a | ||
|
|
7670aff952 | ||
|
|
b72d7934dd | ||
|
|
74673b5449 | ||
|
|
5d1ff43012 | ||
|
|
c39c62b8ec | ||
|
|
8abb752520 | ||
|
|
278f42d4c7 | ||
|
|
c541ac08d4 | ||
|
|
65c7b94c5c | ||
|
|
8774ed096e | ||
|
|
aad8004790 | ||
|
|
500db33fc7 | ||
|
|
467962e565 | ||
|
|
612a62ecac | ||
|
|
167ddf40ab | ||
|
|
a7258b3c81 | ||
|
|
5e02b1e745 | ||
|
|
77d622d460 | ||
|
|
8a8db5c1db | ||
|
|
6632712802 | ||
|
|
a0bf262ebc | ||
|
|
2ef3bf90dd | ||
|
|
4576d87526 | ||
|
|
9a64e72e89 | ||
|
|
d3ea23f3bf | ||
|
|
4d14fd5baf | ||
|
|
9237935f69 | ||
|
|
bc8dfab9b3 | ||
|
|
7234ccf8b0 | ||
|
|
2a99530d18 | ||
|
|
74f4263678 | ||
|
|
917d8da78c | ||
|
|
782e96340e | ||
|
|
39b66f2f8c | ||
|
|
50dd1c3150 | ||
|
|
d2e62949fb | ||
|
|
bad1190a37 | ||
|
|
0faa8655d9 | ||
|
|
5d5e35021e | ||
|
|
264518de89 | ||
|
|
062f95f7c9 | ||
|
|
1db3b504ec | ||
|
|
745a2b7ada | ||
|
|
268ab1da64 | ||
|
|
d25e851edc | ||
|
|
36c9b68e10 | ||
|
|
bc64fd360c | ||
|
|
0d540b707a | ||
|
|
eefc97bdfb | ||
|
|
1bfaa75c12 | ||
|
|
11604a8a56 | ||
|
|
0671fca24f | ||
|
|
2670d79640 | ||
|
|
269212f406 | ||
|
|
587e520881 | ||
|
|
6c72c713be | ||
|
|
97dade4498 | ||
|
|
18ceddba41 | ||
|
|
add117d5b6 | ||
|
|
76c8b1c22f | ||
|
|
105e64cc11 | ||
|
|
30b99d1291 | ||
|
|
9cf851f9ef | ||
|
|
bec5c544f6 | ||
|
|
772794e3e5 | ||
|
|
19e548ea2f | ||
|
|
1d6ad7282c | ||
|
|
a64acc3457 | ||
|
|
0a8748220a | ||
|
|
22ddc6e918 | ||
|
|
2a5abbd7d8 | ||
|
|
5cabb7a458 | ||
|
|
db77b562b1 | ||
|
|
ec17bd741d | ||
|
|
2ad80ba324 | ||
|
|
6b493b25a7 | ||
|
|
f76512d55b | ||
|
|
5f01a960f1 | ||
|
|
e4f6940ecc | ||
|
|
a867a88550 | ||
|
|
53620b6456 | ||
|
|
126ce6f039 | ||
|
|
84dfab759f | ||
|
|
3c626ac2bc | ||
|
|
475ea4c158 | ||
|
|
1d6905332b | ||
|
|
e4180c1808 | ||
|
|
20db190360 | ||
|
|
ad1abc5f61 | ||
|
|
9e3ed840fd | ||
|
|
cbf7e1816a | ||
|
|
7dd34efee5 |
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
tests/pages/
|
||||
.DS_Store
|
||||
85
README.md
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
Save a web page/selection as an eBook (.epub format) - a Chrome/Firefox/Opera Web Extension
|
||||
|
||||

|
||||
<img src="https://github.com/alexadam/save-as-ebook/blob/master/ex11.png?raw=true" width="350">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## How to install it
|
||||
|
||||
### From [Chrome Web Store](https://chrome.google.com/webstore/detail/save-as-ebook/haaplkpoiimngbppjihnegfmpejdnffj)
|
||||
|
|
@ -43,21 +45,91 @@ sudo apt-get install calibre
|
|||
ebook-convert "book.epub" "book.mobi"
|
||||
```
|
||||
|
||||
## Default Keyboard Shortcuts
|
||||
|
||||
**NOTE** These shortcuts are not fixed and the browser will assign a different shortcut if the default one is taken
|
||||
|
||||
| Shortcut | Description |
|
||||
| --- | --- |
|
||||
| Alt + Shift + 1 | Save current page as eBook |
|
||||
| Alt + Shift + 2 | Save current selection as eBook |
|
||||
| Alt + Shift + 3 | Add current page as chapter |
|
||||
| Alt + Shift + 4 | Add current selection as chapter |
|
||||
|
||||
## How to change the default Shortcuts
|
||||
|
||||
in Chrome:
|
||||
|
||||
```
|
||||
1. Navigate to chrome://extensions/
|
||||
2. Scroll down
|
||||
3. Click on Keyboard shortcuts
|
||||
```
|
||||
|
||||
## Added in 1.4.2
|
||||
- Added MIME type to the generated .epub file
|
||||
|
||||
## Added in 1.4.1
|
||||
- Remove unnecessary permissions
|
||||
- Detect image type if the URL doesn't have a file extension (jpg, gif, png)
|
||||
- Reset the Busy indicator on errors
|
||||
- Remove hidden elements when style is not included
|
||||
- Replace iframes with divs
|
||||
|
||||
## Added in 1.4
|
||||
- Smaller ebook file size
|
||||
- Fix for #37 - custom styles not applied
|
||||
- Fix for #36 - br tag missing from pre blocks
|
||||
- Fix for #31 - hanging in Busy state
|
||||
- Other misc bug fixes
|
||||
|
||||
## Added in v1.3.4
|
||||
- Fix for MathML - the rendered expression is too large (Issue #26)
|
||||
- Add translation in Russian (thanks to @ Emil Khalikov) & Brazilian Portuguese (thanks to @welksonramos)
|
||||
|
||||
## Added in v1.3
|
||||
- Keyboard shortcuts
|
||||
- Simplified tool bar menu
|
||||
- Misc bug fixes
|
||||
|
||||
## Added in v1.2.2
|
||||
- fixed & & issue in title; Issue # 10
|
||||
|
||||
## Added in v1.2.1
|
||||
- support for hr/br html tags
|
||||
|
||||
## Added in v1.2
|
||||
- BETA: Support for CSS
|
||||
- BETA: Create / edit custom Styles
|
||||
- No errors from EPUB Validator (http://validator.idpf.org/) + this should fix the Google Play upload issue
|
||||
|
||||
## Added in v1.1
|
||||
- Chapter Editor: option to save changes
|
||||
- Chapter Editor: option to remove all chapters
|
||||
- persist Chapter Editor changes & chapters after generating an eBook or after a browser restart
|
||||
|
||||
## To-Do
|
||||
- fix all 'epubcheck' errors (https://github.com/IDPF/epubcheck)
|
||||
- make the Custom Style Editor more user friendly
|
||||
- support backup / restore for Custom Styles
|
||||
- DONE fix all 'epubcheck' errors (https://github.com/IDPF/epubcheck)
|
||||
- clean & optimize code
|
||||
- create tests
|
||||
- 'save as image' option (render a selection and save it as image instead of text)
|
||||
- support other formats (mobi, pdf etc.)
|
||||
- show confirmations (ui/ux)
|
||||
- display errors (ui/ux)
|
||||
- add settings & options page (ui/ux)
|
||||
- support custom style
|
||||
- DONE support custom style
|
||||
- add 'remove from ebook' right click menu action
|
||||
|
||||
## Run Tests (Work in progress...)
|
||||
```
|
||||
cd tests
|
||||
yarn install # install puppeteer
|
||||
node test/index.js # should start a chrome instance with Save as eBook loaded
|
||||
|
||||
# it will generate and save the ebook in ./tmp-downloads
|
||||
|
||||
....
|
||||
```
|
||||
|
||||
## Credits
|
||||
- http://ebooks.stackexchange.com/questions/1183/what-is-the-minimum-required-content-for-a-valid-epub
|
||||
|
|
@ -65,4 +137,5 @@ ebook-convert "book.epub" "book.mobi"
|
|||
- https://stuk.github.io/jszip/
|
||||
- http://johnny.github.io/jquery-sortable/
|
||||
- https://github.com/eligrey/FileSaver.js/
|
||||
- https://www.iconfinder.com/icons/1031371/book_empty_library_reading_icon#size=128
|
||||
- https://www.iconfinder.com/icons/753890/book_books_education_library_study_icon#size=128
|
||||
- Thanks to [pumpk0n](https://github.com/pumpk0n) and [Francois Bocquet](https://github.com/fbocquet) for helping me with the French translation
|
||||
|
|
|
|||
BIN
ex1.png
|
Before Width: | Height: | Size: 33 KiB |
BIN
ex11.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
ex3.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
tests.tar
52
tests/index.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
const puppeteer = require('puppeteer');
|
||||
const fs = require('fs')
|
||||
|
||||
const CRX_PATH = '../web-extension';
|
||||
const REFERENCE_EBOOK_PATH = 'reference-ebook'
|
||||
const TEST_RESULT_EBOOK_PATH = 'test-result-ebook'
|
||||
const TEST_EBOOK_FILE_NAME = 'test.epub'
|
||||
|
||||
const testPaths = [
|
||||
|
||||
]
|
||||
|
||||
puppeteer.launch({
|
||||
headless: false,
|
||||
args: [
|
||||
`--disable-extensions-except=${CRX_PATH}`,
|
||||
`--load-extension=${CRX_PATH}`,
|
||||
'--user-agent=PuppeteerTestingAgent'
|
||||
]
|
||||
}).then(async browser => {
|
||||
|
||||
prepareTests()
|
||||
|
||||
await runLocalFullPageTests(browser)
|
||||
});
|
||||
|
||||
async function runLocalFullPageTests(browser) {
|
||||
|
||||
const testedFileName = 'mathjax' //'canvas' //'svg' //'special-chars' // 'p2' //'p1'
|
||||
|
||||
const testUrl = 'file://'+__dirname+'/pages/'+testedFileName+'/page/index.html'
|
||||
const resultDownloadPath = './pages/'+testedFileName+'/' + TEST_RESULT_EBOOK_PATH
|
||||
|
||||
const page = await browser.newPage();
|
||||
await page._client.send('Page.setDownloadBehavior', {behavior: 'allow', downloadPath: resultDownloadPath});
|
||||
await page.setViewport({ width: 1280, height: 800 })
|
||||
// await page.goto('https://en.wikipedia.org/wiki/E-book', { waitUntil: 'networkidle0' });
|
||||
// await page.goto('file://'+__dirname+'/../pages/p1/E-book - Wikipedia.html', { waitUntil: 'networkidle0' });
|
||||
await page.goto(testUrl, { waitUntil: 'networkidle0' });
|
||||
}
|
||||
|
||||
function prepareTests() {
|
||||
const testedFileName = 'p1'
|
||||
const pathToDelete = './pages/'+testedFileName+'/'+TEST_RESULT_EBOOK_PATH+'/'+TEST_EBOOK_FILE_NAME
|
||||
try {
|
||||
if (fs.existsSync(pathToDelete)) {
|
||||
fs.unlinkSync(pathToDelete)
|
||||
}
|
||||
} catch(err) {
|
||||
console.log('Error while deleting file ', err);
|
||||
}
|
||||
}
|
||||
9
tests/package.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "web-extension-test",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"puppeteer": "^1.14.0"
|
||||
}
|
||||
}
|
||||
BIN
tests/pages.zip
Normal file
94
web-extension/_locales/en/messages.json
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"extName": {
|
||||
"message": "Save as eBook",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"includeStyle": {
|
||||
"message": "Include (Custom) Style"
|
||||
},
|
||||
"editStyles": {
|
||||
"message": "Edit Custom Styles ..."
|
||||
},
|
||||
"savePage": {
|
||||
"message": "Save Page"
|
||||
},
|
||||
"saveSelection": {
|
||||
"message": "Save Selection"
|
||||
},
|
||||
"pageChapter": {
|
||||
"message": "Add Page as Chapter"
|
||||
},
|
||||
"selectionChapter": {
|
||||
"message": "Add Selection as Chapter"
|
||||
},
|
||||
"editChapters": {
|
||||
"message": "Edit Chapters ..."
|
||||
},
|
||||
"waitMessage": {
|
||||
"message": "Please Wait ..."
|
||||
},
|
||||
"chapterEditorTitle": {
|
||||
"message": "Chapter Editor"
|
||||
},
|
||||
"ebookTitleLabel": {
|
||||
"message": "eBook Title:"
|
||||
},
|
||||
"rawPreview": {
|
||||
"message": "Raw Preview"
|
||||
},
|
||||
"remove": {
|
||||
"message": "Remove"
|
||||
},
|
||||
"removeChapters": {
|
||||
"message": "Remove Chapters"
|
||||
},
|
||||
"removeChaptersConfirm": {
|
||||
"message": "Do you want to remove all chapters?"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Cancel"
|
||||
},
|
||||
"generateEbook": {
|
||||
"message": "Generate eBook ..."
|
||||
},
|
||||
"saveChanges": {
|
||||
"message": "Save changes"
|
||||
},
|
||||
"emptyBookWarning": {
|
||||
"message": "Can't generate an empty eBook!"
|
||||
},
|
||||
"confirmDeleteStyle": {
|
||||
"message": "Are you sure you want to delete this style?"
|
||||
},
|
||||
"invalidRegex": {
|
||||
"message": "Invalid regular expression"
|
||||
},
|
||||
"styleSaved": {
|
||||
"message": "Style saved!"
|
||||
},
|
||||
"styleEditor": {
|
||||
"message": "Style Editor"
|
||||
},
|
||||
"selectExistingCSS": {
|
||||
"message": "Select Existing CSS"
|
||||
},
|
||||
"orLabel": {
|
||||
"message": "or"
|
||||
},
|
||||
"createNewStyle": {
|
||||
"message": "Create New Style"
|
||||
},
|
||||
"styleNameLabel": {
|
||||
"message": "Name"
|
||||
},
|
||||
"howToWriteRegexLabel": {
|
||||
"message": "How to write a regular expression pattern"
|
||||
},
|
||||
"removeStyle": {
|
||||
"message": "Remove Style"
|
||||
},
|
||||
"saveStyle": {
|
||||
"message": "Save Style"
|
||||
}
|
||||
|
||||
}
|
||||
99
web-extension/_locales/fr/messages.json
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"extName": {
|
||||
"message": "Enregistrer comme eBook",
|
||||
"description": "Nom de l'extension"
|
||||
},
|
||||
"includeStyle": {
|
||||
"message": "Inclure le style"
|
||||
},
|
||||
"styleLabel": {
|
||||
"message": "Style personnalisé :"
|
||||
},
|
||||
"applyStyle": {
|
||||
"message": "Appliquer"
|
||||
},
|
||||
"editStyles": {
|
||||
"message": "Éditer les styles..."
|
||||
},
|
||||
"savePage": {
|
||||
"message": "Enregistrer la page"
|
||||
},
|
||||
"saveSelection": {
|
||||
"message": "Enregistrer la sélection"
|
||||
},
|
||||
"pageChapter": {
|
||||
"message": "Ajouter la page en tant que chapitre"
|
||||
},
|
||||
"selectionChapter": {
|
||||
"message": "Ajouter la sélection en tant que chapitre"
|
||||
},
|
||||
"editChapters": {
|
||||
"message": "Modifier les chapitres..."
|
||||
},
|
||||
"waitMessage": {
|
||||
"message": "Merci de patienter..."
|
||||
},
|
||||
"chapterEditorTitle": {
|
||||
"message": "Éditeur de chapitre"
|
||||
},
|
||||
"ebookTitleLabel": {
|
||||
"message": "Titre de l'eBook :"
|
||||
},
|
||||
"rawPreview": {
|
||||
"message": "Aperçu brut"
|
||||
},
|
||||
"remove": {
|
||||
"message": "Retirer"
|
||||
},
|
||||
"removeChapters": {
|
||||
"message": "Supprimer les chapitres"
|
||||
},
|
||||
"removeChaptersConfirm": {
|
||||
"message": "Voulez-vous supprimer tous les chapitres ?"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Annuler"
|
||||
},
|
||||
"generateEbook": {
|
||||
"message": "Générer l'eBook..."
|
||||
},
|
||||
"saveChanges": {
|
||||
"message": "Enregistrer les modifications"
|
||||
},
|
||||
"emptyBookWarning": {
|
||||
"message": "Impossible de générer un eBook vide !"
|
||||
},
|
||||
"confirmDeleteStyle": {
|
||||
"message": "Êtes-vous sûr de vouloir supprimer ce style ?"
|
||||
},
|
||||
"invalidRegex": {
|
||||
"message": "Expression régulière invalide"
|
||||
},
|
||||
"styleSaved": {
|
||||
"message": "Style enregistré !"
|
||||
},
|
||||
"styleEditor": {
|
||||
"message": "Éditeur de style"
|
||||
},
|
||||
"selectExistingCSS": {
|
||||
"message": "Sélectionner un style existant"
|
||||
},
|
||||
"orLabel": {
|
||||
"message": "ou"
|
||||
},
|
||||
"createNewStyle": {
|
||||
"message": "Créer un nouveau style"
|
||||
},
|
||||
"styleNameLabel": {
|
||||
"message": "Nom"
|
||||
},
|
||||
"howToWriteRegexLabel": {
|
||||
"message": "Comment écrire une expression régulière"
|
||||
},
|
||||
"removeStyle": {
|
||||
"message": "Supprimer le style"
|
||||
},
|
||||
"saveStyle": {
|
||||
"message": "Enregistrer le style"
|
||||
}
|
||||
}
|
||||
93
web-extension/_locales/pt_BR/messages.json
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"extName": {
|
||||
"message": "Salvar como eBook",
|
||||
"description": "Nome da extensão"
|
||||
},
|
||||
"includeStyle": {
|
||||
"message": "Incuir estilo (personalizado)"
|
||||
},
|
||||
"editStyles": {
|
||||
"message": "Editar estilos personalizados ..."
|
||||
},
|
||||
"savePage": {
|
||||
"message": "Salvar Página"
|
||||
},
|
||||
"saveSelection": {
|
||||
"message": "Salvar Selecão"
|
||||
},
|
||||
"pageChapter": {
|
||||
"message": "Adicionar página como capítulo"
|
||||
},
|
||||
"selectionChapter": {
|
||||
"message": "Adicionar seleção como capítulo"
|
||||
},
|
||||
"editChapters": {
|
||||
"message": "Editar Capítulos ..."
|
||||
},
|
||||
"waitMessage": {
|
||||
"message": "Por favor, aguarde ..."
|
||||
},
|
||||
"chapterEditorTitle": {
|
||||
"message": "Editor de capítulos"
|
||||
},
|
||||
"ebookTitleLabel": {
|
||||
"message": "Título do eBook:"
|
||||
},
|
||||
"rawPreview": {
|
||||
"message": "Pré-visualização"
|
||||
},
|
||||
"remove": {
|
||||
"message": "Remover"
|
||||
},
|
||||
"removeChapters": {
|
||||
"message": "Remover capítulos"
|
||||
},
|
||||
"removeChaptersConfirm": {
|
||||
"message": "Você quer remover todos os capítulos?"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Cancelar"
|
||||
},
|
||||
"generateEbook": {
|
||||
"message": "Gerar eBook ..."
|
||||
},
|
||||
"saveChanges": {
|
||||
"message": "Salvar alterações"
|
||||
},
|
||||
"emptyBookWarning": {
|
||||
"message": "Não é possível gerar um eBook vazio!"
|
||||
},
|
||||
"confirmDeleteStyle": {
|
||||
"message": "Tem certeza de que deseja excluir este estilo?"
|
||||
},
|
||||
"invalidRegex": {
|
||||
"message": "Expressão regular inválida"
|
||||
},
|
||||
"styleSaved": {
|
||||
"message": "Estilo salvo!"
|
||||
},
|
||||
"styleEditor": {
|
||||
"message": "Editor de estilo"
|
||||
},
|
||||
"selectExistingCSS": {
|
||||
"message": "Selecionar CSS Existente"
|
||||
},
|
||||
"orLabel": {
|
||||
"message": "ou"
|
||||
},
|
||||
"createNewStyle": {
|
||||
"message": "Criar Novo Estilo"
|
||||
},
|
||||
"styleNameLabel": {
|
||||
"message": "Nome"
|
||||
},
|
||||
"howToWriteRegexLabel": {
|
||||
"message": "Como escrever um padrão de expressão regular"
|
||||
},
|
||||
"removeStyle": {
|
||||
"message": "Remover Estilo"
|
||||
},
|
||||
"saveStyle": {
|
||||
"message": "Salvar Estilo"
|
||||
}
|
||||
}
|
||||
93
web-extension/_locales/ru/messages.json
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"extName": {
|
||||
"message": "Save as eBook",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"includeStyle": {
|
||||
"message": "Подключить CSS"
|
||||
},
|
||||
"editStyles": {
|
||||
"message": "Редактировать CSS ..."
|
||||
},
|
||||
"savePage": {
|
||||
"message": "Сохранить страницу"
|
||||
},
|
||||
"saveSelection": {
|
||||
"message": "Сохранить выделение"
|
||||
},
|
||||
"pageChapter": {
|
||||
"message": "Добавить страницу как главу"
|
||||
},
|
||||
"selectionChapter": {
|
||||
"message": "Добавить выделение как главу"
|
||||
},
|
||||
"editChapters": {
|
||||
"message": "Редактировать главы ..."
|
||||
},
|
||||
"waitMessage": {
|
||||
"message": "Пожалуйста ожидайте ..."
|
||||
},
|
||||
"chapterEditorTitle": {
|
||||
"message": "Редактор глав"
|
||||
},
|
||||
"ebookTitleLabel": {
|
||||
"message": "Заголовок книги:"
|
||||
},
|
||||
"rawPreview": {
|
||||
"message": "Предпросмотр"
|
||||
},
|
||||
"remove": {
|
||||
"message": "Удалить"
|
||||
},
|
||||
"removeChapters": {
|
||||
"message": "Удалить главы"
|
||||
},
|
||||
"removeChaptersConfirm": {
|
||||
"message": "Вы точно хотите удалить все главы?"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Отмена"
|
||||
},
|
||||
"generateEbook": {
|
||||
"message": "Создать книгу ..."
|
||||
},
|
||||
"saveChanges": {
|
||||
"message": "Сохранить изменения"
|
||||
},
|
||||
"emptyBookWarning": {
|
||||
"message": "Невозможно создать пустую книгу"
|
||||
},
|
||||
"confirmDeleteStyle": {
|
||||
"message": "Вы точно хотите удалить этот стиль?"
|
||||
},
|
||||
"invalidRegex": {
|
||||
"message": "Ошибка в регулярном выражении"
|
||||
},
|
||||
"styleSaved": {
|
||||
"message": "Стиль сохранен!"
|
||||
},
|
||||
"styleEditor": {
|
||||
"message": "Редактор стилей"
|
||||
},
|
||||
"selectExistingCSS": {
|
||||
"message": "Выбрать существующий стиль"
|
||||
},
|
||||
"orLabel": {
|
||||
"message": "или"
|
||||
},
|
||||
"createNewStyle": {
|
||||
"message": "Создать новый стиль"
|
||||
},
|
||||
"styleNameLabel": {
|
||||
"message": "Название"
|
||||
},
|
||||
"howToWriteRegexLabel": {
|
||||
"message": "Введите регулярное выражение для адреса"
|
||||
},
|
||||
"removeStyle": {
|
||||
"message": "Удалить стиль"
|
||||
},
|
||||
"saveStyle": {
|
||||
"message": "Сохранить стиль"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,354 @@
|
|||
|
||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
///////////////////
|
||||
///////////////////
|
||||
///////////////////
|
||||
///////////////////
|
||||
/// Only for testing
|
||||
|
||||
chrome.runtime.onInstalled.addListener(details => {
|
||||
if (navigator.userAgent === 'PuppeteerTestingAgent') {
|
||||
let TEST_TIMER = null
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (TEST_TIMER) {
|
||||
clearTimeout(TEST_TIMER)
|
||||
}
|
||||
|
||||
TEST_TIMER = setTimeout(()=> {
|
||||
executeCommand({type: 'save-page'})
|
||||
}, 2000)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
///////////////////
|
||||
///////////////////
|
||||
///////////////////
|
||||
///////////////////
|
||||
|
||||
var isBusy = false;
|
||||
var busyResetTimer = null
|
||||
|
||||
var defaultStyles = [
|
||||
{
|
||||
title: 'Reddit Comments',
|
||||
url: 'reddit\\.com\\/r\\/[^\\/]+\\/comments',
|
||||
style: `.side {
|
||||
display: none;
|
||||
}
|
||||
#header {
|
||||
display: none;
|
||||
}
|
||||
.arrow, .expand, .score, .live-timestamp, .flat-list, .buttons, .morecomments, .footer-parent, .icon {
|
||||
display: none !important;
|
||||
}
|
||||
`
|
||||
},{
|
||||
title: 'Wikipedia Article',
|
||||
url: 'wikipedia\\.org\\/wiki\\/',
|
||||
style: `#mw-navigation {
|
||||
display: none;
|
||||
}
|
||||
#footer {
|
||||
display: none;
|
||||
}
|
||||
#mw-panel {
|
||||
display: none;
|
||||
}
|
||||
#mw-head {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
},{
|
||||
title: 'YCombinator News Comments',
|
||||
url: 'news\\.ycombinator\\.com\\/item\\?id=[0-9]+',
|
||||
style: `#hnmain > tbody > tr:nth-child(1) > td > table {
|
||||
display: none;
|
||||
}
|
||||
* {
|
||||
background-color: white;
|
||||
}
|
||||
.title, .storylink {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
.score {
|
||||
display: none;
|
||||
}
|
||||
.age {
|
||||
display: none;
|
||||
}
|
||||
.hnpast {
|
||||
display: none;
|
||||
}
|
||||
.togg {
|
||||
display: none;
|
||||
}
|
||||
.votelinks, .rank {
|
||||
display: none;
|
||||
}
|
||||
.votearrow {
|
||||
display: none;
|
||||
}
|
||||
.yclinks {
|
||||
display: none;
|
||||
}
|
||||
form {
|
||||
display: none;
|
||||
}
|
||||
a.hnuser {
|
||||
font-weight: bold;
|
||||
color: black !important;
|
||||
padding: 3px;
|
||||
}
|
||||
.subtext > span, .subtext > a:not(:nth-child(2)) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
},{
|
||||
title: 'Medium Article',
|
||||
url: 'medium\\.com',
|
||||
style: `.metabar {
|
||||
display: none !important;
|
||||
}
|
||||
header.container {
|
||||
display: none;
|
||||
}
|
||||
.js-postShareWidget {
|
||||
display: none;
|
||||
}
|
||||
footer, canvas {
|
||||
display: none !important;
|
||||
}
|
||||
.u-fixed, .u-bottom0 {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
},{
|
||||
title: 'Twitter',
|
||||
url: 'twitter\\.com\\/.+',
|
||||
style: `.topbar {
|
||||
display: none !important;
|
||||
}
|
||||
.ProfileCanopy, .ProfileCanopy-inner {
|
||||
display: none;
|
||||
}
|
||||
.ProfileSidebar {
|
||||
display: none;
|
||||
}
|
||||
.ProfileHeading {
|
||||
display: none !important;
|
||||
}
|
||||
.ProfileTweet-actionList {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
chrome.commands.onCommand.addListener((command) => {
|
||||
executeCommand({type: command})
|
||||
});
|
||||
|
||||
function executeCommand(command) {
|
||||
if (isBusy) {
|
||||
chrome.tabs.query({
|
||||
currentWindow: true,
|
||||
active: true
|
||||
}, (tab) => {
|
||||
chrome.tabs.sendMessage(tab[0].id, {'alert': 'Work in progress! Please wait until the current eBook is generated!'}, (r) => {
|
||||
console.log(r);
|
||||
});
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (command.type === 'save-page') {
|
||||
dispatch('extract-page', false, []);
|
||||
} else if (command.type === 'save-selection') {
|
||||
dispatch('extract-selection', false, []);
|
||||
} else if (command.type === 'add-page') {
|
||||
dispatch('extract-page', true, []);
|
||||
} else if (command.type === 'add-selection') {
|
||||
dispatch('extract-selection', true, []);
|
||||
}
|
||||
|
||||
isBusy = true
|
||||
|
||||
//
|
||||
busyResetTimer = setTimeout(() => {
|
||||
resetBusy()
|
||||
}, 20000)
|
||||
}
|
||||
|
||||
function dispatch(action, justAddToBuffer, appliedStyles) {
|
||||
if (!justAddToBuffer) {
|
||||
_execRequest({type: 'remove'});
|
||||
}
|
||||
chrome.browserAction.setBadgeBackgroundColor({color:"red"});
|
||||
chrome.browserAction.setBadgeText({text: "Busy"});
|
||||
|
||||
chrome.tabs.query({
|
||||
currentWindow: true,
|
||||
active: true
|
||||
}, (tab) => {
|
||||
|
||||
isIncludeStyles((result) =>{
|
||||
let isIncludeStyle = result.includeStyle
|
||||
prepareStyles(tab, isIncludeStyle, appliedStyles, (tmpAppliedStyles) => {
|
||||
applyAction(tab, action, justAddToBuffer, isIncludeStyle, tmpAppliedStyles, () => {
|
||||
alert('done')
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function isIncludeStyles(callback) {
|
||||
chrome.storage.local.get('includeStyle', (data) => {
|
||||
if (!data) {
|
||||
callback({includeStyle: false});
|
||||
} else {
|
||||
callback({includeStyle: data.includeStyle});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function prepareStyles(tab, includeStyle, appliedStyles, callback) {
|
||||
if (!includeStyle) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
chrome.storage.local.get('styles', (data) => {
|
||||
let styles = defaultStyles;
|
||||
if (data && data.styles) {
|
||||
styles = data.styles;
|
||||
}
|
||||
let currentUrl = tab[0].url;
|
||||
let currentStyle = null;
|
||||
|
||||
if (!styles) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
if (styles.length === 0) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
let allMatchingStyles = [];
|
||||
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
currentUrl = currentUrl.replace(/(http[s]?:\/\/|www\.)/i, '').toLowerCase();
|
||||
let styleUrl = styles[i].url;
|
||||
let styleUrlRegex = null;
|
||||
|
||||
try {
|
||||
styleUrlRegex = new RegExp(styleUrl, 'i');
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if (styleUrlRegex && styleUrlRegex.test(currentUrl)) {
|
||||
allMatchingStyles.push({
|
||||
index: i,
|
||||
length: styleUrl.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (allMatchingStyles.length === 0) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
allMatchingStyles.sort((a, b) => b.length - a.length);
|
||||
let selStyle = allMatchingStyles[0];
|
||||
|
||||
if (!selStyle) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
currentStyle = styles[selStyle.index];
|
||||
|
||||
if (!currentStyle) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentStyle.style) {
|
||||
callback(appliedStyles)
|
||||
return
|
||||
}
|
||||
|
||||
chrome.tabs.insertCSS(tab[0].id, { code: currentStyle.style }, () => {
|
||||
appliedStyles.push(currentStyle);
|
||||
callback(appliedStyles)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function applyAction(tab, action, justAddToBuffer, includeStyle, appliedStyles, callback) {
|
||||
chrome.tabs.sendMessage(tab[0].id, {
|
||||
type: action,
|
||||
includeStyle: includeStyle,
|
||||
appliedStyles: appliedStyles
|
||||
}, (response) => {
|
||||
|
||||
if (!response) {
|
||||
resetBusy()
|
||||
chrome.tabs.sendMessage(tab[0].id, {'alert': 'Save as eBook does not work on this web site!'}, (r) => {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.content.trim() === '') {
|
||||
resetBusy()
|
||||
if (justAddToBuffer) {
|
||||
chrome.tabs.sendMessage(tab[0].id, {'alert': 'Cannot add an empty selection as chapter!'}, (r) => {});
|
||||
} else {
|
||||
chrome.tabs.sendMessage(tab[0].id, {'alert': 'Cannot generate the eBook from an empty selection!'}, (r) => {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!justAddToBuffer) {
|
||||
chrome.tabs.sendMessage(tab[0].id, {'shortcut': 'build-ebook', response: [response]}, (r) => {});
|
||||
} else {
|
||||
chrome.storage.local.get('allPages', (data) => {
|
||||
if (!data || !data.allPages) {
|
||||
data.allPages = [];
|
||||
}
|
||||
data.allPages.push(response);
|
||||
chrome.storage.local.set({'allPages': data.allPages});
|
||||
resetBusy()
|
||||
chrome.tabs.sendMessage(tab[0].id, {'alert': 'Page or selection added as chapter!'}, (r) => {});
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetBusy() {
|
||||
isBusy = false
|
||||
|
||||
if (busyResetTimer) {
|
||||
clearTimeout(busyResetTimer)
|
||||
busyResetTimer = null
|
||||
}
|
||||
|
||||
chrome.browserAction.setBadgeText({text: ""})
|
||||
|
||||
let popups = chrome.extension.getViews({type: "popup"});
|
||||
if (popups && popups.length > 0) {
|
||||
popups[0].close()
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(_execRequest);
|
||||
|
||||
function _execRequest(request, sender, sendResponse) {
|
||||
if (request.type === 'get') {
|
||||
chrome.storage.local.get('allPages', function (data) {
|
||||
if (!data || !data.allPages) {
|
||||
|
|
@ -27,5 +376,54 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
|||
if (request.type === 'set title') {
|
||||
chrome.storage.local.set({'title': request.title});
|
||||
}
|
||||
if (request.type === 'get styles') {
|
||||
chrome.storage.local.get('styles', function (data) {
|
||||
if (!data || !data.styles) {
|
||||
sendResponse({styles: defaultStyles});
|
||||
} else {
|
||||
sendResponse({styles: data.styles});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (request.type === 'set styles') {
|
||||
chrome.storage.local.set({'styles': request.styles});
|
||||
}
|
||||
if (request.type === 'get current style') {
|
||||
chrome.storage.local.get('currentStyle', function (data) {
|
||||
if (!data || !data.currentStyle) {
|
||||
sendResponse({currentStyle: 0});
|
||||
} else {
|
||||
sendResponse({currentStyle: data.currentStyle});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (request.type === 'set current style') {
|
||||
chrome.storage.local.set({'currentStyle': request.currentStyle});
|
||||
}
|
||||
if (request.type === 'get include style') {
|
||||
chrome.storage.local.get('includeStyle', function (data) {
|
||||
if (!data) {
|
||||
sendResponse({includeStyle: false});
|
||||
} else {
|
||||
sendResponse({includeStyle: data.includeStyle});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (request.type === 'set include style') {
|
||||
chrome.storage.local.set({'includeStyle': request.includeStyle});
|
||||
}
|
||||
if (request.type === 'is busy?') {
|
||||
sendResponse({isBusy: isBusy})
|
||||
}
|
||||
if (request.type === 'set is busy') {
|
||||
isBusy = request.isBusy
|
||||
}
|
||||
if (request.type === 'save-page' || request.type === 'save-selection' ||
|
||||
request.type === 'add-page' || request.type === 'add-selection') {
|
||||
executeCommand({type: request.type})
|
||||
}
|
||||
if (request.type === 'done') {
|
||||
resetBusy()
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ ul.chapterEditor-chapters-list li.placeholder {
|
|||
font-weight: bold;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
font-family: "sans-serif";
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#chapterEditor-ebookTitleHolder {
|
||||
|
|
@ -35,7 +35,7 @@ ul.chapterEditor-chapters-list li.placeholder {
|
|||
#chapterEditor-ebookTitle {
|
||||
padding: 5px;
|
||||
font-size: 15px;
|
||||
font-family: "sans-serif";
|
||||
font-family: sans-serif;
|
||||
width: 85%;
|
||||
}
|
||||
ul, ul.chapterEditor-chapters-list {
|
||||
|
|
@ -66,7 +66,7 @@ ul, ul.chapterEditor-chapters-list {
|
|||
.chapterEditor-chapter-item > input[type="text"] {
|
||||
padding: 5px;
|
||||
font-size: 15px;
|
||||
font-family: "sans-serif";
|
||||
font-family: sans-serif;
|
||||
width: 80%;
|
||||
border: none;
|
||||
border-bottom: solid 1px #aaa;
|
||||
|
|
@ -79,9 +79,6 @@ ul, ul.chapterEditor-chapters-list {
|
|||
}
|
||||
#chapterEditor-modalList {
|
||||
display: block;
|
||||
/*width: 100%;*/
|
||||
/*border-top: solid 1px black;*/
|
||||
/*border-bottom: solid 1px black;*/
|
||||
padding: 20px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
|
@ -100,7 +97,7 @@ ul, ul.chapterEditor-chapters-list {
|
|||
.chapterEditor-text-button {
|
||||
border: none;
|
||||
font-size: 15px;
|
||||
font-family: "sans-serif";
|
||||
font-family: sans-serif;
|
||||
padding: 5px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
margin: 0 3px;
|
||||
|
|
@ -123,6 +120,8 @@ ul, ul.chapterEditor-chapters-list {
|
|||
}
|
||||
|
||||
.chapterEditor-footer-button {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 18px 20px;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
|
|
@ -135,10 +134,34 @@ ul, ul.chapterEditor-chapters-list {
|
|||
background-color: rgba(0, 0, 0, 1);
|
||||
cursor: pointer;
|
||||
}
|
||||
.chapterEditor-generate-button {
|
||||
.chapterEditor-footer-button:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, .5);
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
transform: scale(1, 1) translate(-50%);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
@keyframes ripple {
|
||||
0% { transform: scale( 0, 0); opacity: 1; }
|
||||
20% { transform: scale(25, 25); opacity: 1; }
|
||||
100% { transform: scale(40, 40); opacity: 0; }
|
||||
}
|
||||
.chapterEditor-footer-button:focus:not(:active)::after {
|
||||
animation: ripple 1s ease-out;
|
||||
}
|
||||
.chapterEditor-footer-button.chapterEditor-generate-button {
|
||||
background-color: yellow;
|
||||
}
|
||||
.chapterEditor-cancel-button {
|
||||
.chapterEditor-footer-button.chapterEditor-generate-button:hover {
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
.chapterEditor-footer-button.chapterEditor-cancel-button {
|
||||
/*background-color: black;*/
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ function showEditor() {
|
|||
// Header
|
||||
var title = document.createElement('span');
|
||||
title.id = "chapterEditor-Title";
|
||||
title.innerText = "Chapter Editor";
|
||||
title.innerText = chrome.i18n.getMessage('chapterEditorTitle');
|
||||
var upperCloseButton = document.createElement('button');
|
||||
modalHeader.appendChild(title);
|
||||
upperCloseButton.onclick = closeModal;
|
||||
|
|
@ -41,7 +41,7 @@ function showEditor() {
|
|||
|
||||
var ebookTilteLabel = document.createElement('span');
|
||||
ebookTilteLabel.id = 'chapterEditor-ebookTitleLabel';
|
||||
ebookTilteLabel.innerText = 'eBook Title: ';
|
||||
ebookTilteLabel.innerText = chrome.i18n.getMessage('ebookTitleLabel');
|
||||
titleHolder.appendChild(ebookTilteLabel);
|
||||
|
||||
var ebookTilte = document.createElement('input');
|
||||
|
|
@ -81,12 +81,12 @@ function showEditor() {
|
|||
var buttons = document.createElement('span');
|
||||
|
||||
var previewButton = document.createElement('button');
|
||||
previewButton.innerText = 'Raw Preview';
|
||||
previewButton.innerText = chrome.i18n.getMessage('rawPreview');
|
||||
previewButton.className = 'chapterEditor-text-button';
|
||||
previewButton.onclick = previewListItem(i);
|
||||
|
||||
var removeButton = document.createElement('button');
|
||||
removeButton.innerText = 'Remove';
|
||||
removeButton.innerText = chrome.i18n.getMessage('remove');
|
||||
removeButton.className = 'chapterEditor-text-button chapterEditor-text-red';
|
||||
removeButton.onclick = removeListItem(i);
|
||||
|
||||
|
|
@ -108,16 +108,16 @@ function showEditor() {
|
|||
// Footer
|
||||
var buttons = document.createElement('div');
|
||||
var closeButton = document.createElement('button');
|
||||
closeButton.innerText = 'Cancel';
|
||||
closeButton.innerText = chrome.i18n.getMessage('cancel');
|
||||
closeButton.className = 'chapterEditor-footer-button chapterEditor-float-left chapterEditor-cancel-button';
|
||||
closeButton.onclick = closeModal;
|
||||
buttons.appendChild(closeButton);
|
||||
|
||||
var removeButton = document.createElement('button');
|
||||
removeButton.innerText = 'Remove Chapters';
|
||||
removeButton.innerText = chrome.i18n.getMessage('removeChapters');
|
||||
removeButton.className = 'chapterEditor-footer-button hapterEditor-float-left';
|
||||
removeButton.onclick = function() {
|
||||
var result = confirm("Do you want to remove all chapters?");
|
||||
var result = confirm(chrome.i18n.getMessage('removeChaptersConfirm'));
|
||||
if (result) {
|
||||
removeEbook();
|
||||
closeModal();
|
||||
|
|
@ -130,7 +130,7 @@ function showEditor() {
|
|||
var newChapters = saveChanges();
|
||||
prepareEbook(newChapters);
|
||||
};
|
||||
saveButton.innerText = 'Generate eBook ...';
|
||||
saveButton.innerText = chrome.i18n.getMessage('generateEbook');
|
||||
saveButton.className = 'chapterEditor-footer-button chapterEditor-float-right chapterEditor-generate-button';
|
||||
buttons.appendChild(saveButton);
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ function showEditor() {
|
|||
saveChangesButton.onclick = function() {
|
||||
saveChanges();
|
||||
};
|
||||
saveChangesButton.innerText = 'Save changes';
|
||||
saveChangesButton.innerText = chrome.i18n.getMessage('saveChanges');
|
||||
saveChangesButton.className = 'chapterEditor-footer-button chapterEditor-float-right';
|
||||
buttons.appendChild(saveChangesButton);
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ function showEditor() {
|
|||
function prepareEbook(newChapters) {
|
||||
try {
|
||||
if (newChapters.length === 0) {
|
||||
alert('Can\'t generate an empty eBook!');
|
||||
alert(chrome.i18n.getMessage('emptyBookWarning'));
|
||||
return;
|
||||
}
|
||||
buildEbookFromChapters();
|
||||
|
|
@ -236,8 +236,11 @@ function showEditor() {
|
|||
}
|
||||
|
||||
for (var i = 0; i < tmpChaptersList.length; i++) {
|
||||
var listIndex = Number(tmpChaptersList[i].id.replace('li', ''));
|
||||
var tmpChapterItem = tmpChaptersList[i];
|
||||
var listIndex = Number(tmpChapterItem.id.replace('li', ''));
|
||||
if (allPagesRef[listIndex].removed === false) {
|
||||
var newChapterTitle = tmpChapterItem.children.namedItem('text'+listIndex).value;
|
||||
allPagesRef[listIndex].title = newChapterTitle;
|
||||
newChapters.push(allPagesRef[listIndex]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
190
web-extension/cssEditor.css
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#cssEditor-removeStyle {
|
||||
display: none;
|
||||
}
|
||||
#cssEditor-saveStyle {
|
||||
display: none;
|
||||
}
|
||||
#cssEditor-selectStyle {
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#cssEditor-selectStyle > option {
|
||||
padding: 3px;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#cssEditor-orLabel {
|
||||
padding: 3px;
|
||||
margin: 0 3px;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#cssEditor-createNewStyle {
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.cssEditor-field-label-holder {
|
||||
margin-top: 10px;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 90%;
|
||||
}
|
||||
.cssEditor-field-holder {
|
||||
padding: 3px;
|
||||
width: 90%;
|
||||
}
|
||||
.cssEditor-field-label {
|
||||
padding: 0 3px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#cssEditor-styleEditor {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.cssEditor-left-panel {
|
||||
width: 48%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.cssEditor-right-panel {
|
||||
width: 48%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
#cssEditor-styleName {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
padding: 5px;
|
||||
border: solid rgba(0, 0, 0, 0.25) 1px;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#cssEditor-matchUrl {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
padding: 5px;
|
||||
border: solid rgba(0, 0, 0, 0.25) 1px;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#cssEditor-styleContent {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
padding: 5px;
|
||||
border: solid rgba(0, 0, 0, 0.25) 1px;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#cssEditor-Title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#cssEditor-ebookTitleHolder {
|
||||
background-color: #eee;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#cssEditor-modalHeader {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
}
|
||||
#cssEditor-modalList {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
padding-top: 0;
|
||||
}
|
||||
#cssEditor-modalFooter {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cssEditor-text-button {
|
||||
border: none;
|
||||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
padding: 5px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
margin: 0 3px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cssEditor-text-button:hover {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
.cssEditor-text-red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.cssEditor-float-left {
|
||||
float: left;
|
||||
}
|
||||
.cssEditor-float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.cssEditor-footer-button {
|
||||
padding: 18px 20px;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
border: none;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
display: inline-block;
|
||||
}
|
||||
.cssEditor-footer-button:hover {
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cssEditor-save-button {
|
||||
background-color: yellow;
|
||||
}
|
||||
.cssEditor-cancel-button {
|
||||
/*background-color: black;*/
|
||||
}
|
||||
|
||||
@media (max-width: 1700px) {
|
||||
.cssEditor-text-button {
|
||||
font-size: 12px;
|
||||
padding: 3px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
.cssEditor-footer-button {
|
||||
padding: 15px 20px;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.cssEditor-chapter-item {
|
||||
line-height: 30px;
|
||||
height: 60px;
|
||||
padding: 4px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.cssEditor-chapter-item > input[type="text"] {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
379
web-extension/cssEditor.js
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
for (var i=0; i<document.styleSheets.length; i++) {
|
||||
document.styleSheets.item(i).disabled = true;
|
||||
}
|
||||
|
||||
var tmp = document.getElementById('cssEditor-Modal');
|
||||
if (tmp) {
|
||||
tmp.parentNode.removeChild(tmp);
|
||||
}
|
||||
|
||||
var allPagesRef = null;
|
||||
var allStyles = [];
|
||||
var currentStyle = null;
|
||||
var currentStyleIndex = -1;
|
||||
|
||||
|
||||
showEditor();
|
||||
|
||||
|
||||
function showEditor() {
|
||||
|
||||
var body = document.getElementsByTagName('body')[0];
|
||||
var modalContent = document.createElement('div');
|
||||
modalContent.id = 'cssEditor-modalContent';
|
||||
var modalHeader = document.createElement('div');
|
||||
modalHeader.id = 'cssEditor-modalHeader';
|
||||
var modalList = document.createElement('div');
|
||||
modalList.id = 'cssEditor-modalList';
|
||||
var modalFooter = document.createElement('div');
|
||||
modalFooter.id = 'cssEditor-modalFooter';
|
||||
|
||||
////////
|
||||
// Header
|
||||
var title = document.createElement('span');
|
||||
title.id = "cssEditor-Title";
|
||||
title.innerText = chrome.i18n.getMessage('styleEditor');
|
||||
var upperCloseButton = document.createElement('button');
|
||||
modalHeader.appendChild(title);
|
||||
upperCloseButton.onclick = closeModal;
|
||||
upperCloseButton.innerText = 'X';
|
||||
upperCloseButton.className = 'cssEditor-text-button cssEditor-float-right';
|
||||
modalHeader.appendChild(upperCloseButton);
|
||||
/////////////////////
|
||||
// Content List
|
||||
|
||||
var titleHolder = document.createElement('div');
|
||||
titleHolder.id = 'cssEditor-ebookTitleHolder';
|
||||
|
||||
var existingStyles = document.createElement('select');
|
||||
existingStyles.id = "cssEditor-selectStyle";
|
||||
existingStyles.onchange = function (event) {
|
||||
if (existingStyles.selectedIndex === 0) {
|
||||
currentStyle = null;
|
||||
currentStyleIndex = -1;
|
||||
hideStyleEditor();
|
||||
hideRemoveStyle();
|
||||
hideSaveStyle();
|
||||
return;
|
||||
}
|
||||
currentStyle = allStyles[existingStyles.selectedIndex - 1];
|
||||
currentStyleIndex = existingStyles.selectedIndex - 1;
|
||||
editCurrentStyle();
|
||||
};
|
||||
var defaultOption = document.createElement('option');
|
||||
defaultOption.innerText = chrome.i18n.getMessage('selectExistingCSS');
|
||||
existingStyles.appendChild(defaultOption);
|
||||
titleHolder.appendChild(existingStyles);
|
||||
|
||||
var titleLabel = document.createElement('label');
|
||||
titleLabel.id = 'cssEditor-orLabel';
|
||||
titleLabel.innerText = chrome.i18n.getMessage('orLabel');
|
||||
titleHolder.appendChild(titleLabel);
|
||||
|
||||
var createNewStyleButton = document.createElement('button');
|
||||
createNewStyleButton.id = 'cssEditor-createNewStyle';
|
||||
createNewStyleButton.innerText = chrome.i18n.getMessage('createNewStyle');
|
||||
createNewStyleButton.onclick = createNewStyle;
|
||||
titleHolder.appendChild(createNewStyleButton);
|
||||
|
||||
modalList.appendChild(titleHolder);
|
||||
|
||||
function createNewStyle() {
|
||||
currentStyle = null;
|
||||
currentStyleIndex = -1;
|
||||
resetFields();
|
||||
showStyleEditor();
|
||||
showSaveStyle();
|
||||
hideRemoveStyle();
|
||||
createStyleList();
|
||||
}
|
||||
|
||||
//////
|
||||
|
||||
var editorHolder = document.createElement('div');
|
||||
editorHolder.style.display = 'none';
|
||||
editorHolder.id = 'cssEditor-styleEditor';
|
||||
|
||||
var editorHolderLeft = document.createElement('div');
|
||||
editorHolderLeft.className = 'cssEditor-left-panel';
|
||||
var editorHolderRight = document.createElement('div');
|
||||
editorHolderRight.className = 'cssEditor-right-panel';
|
||||
|
||||
var nameLabelHolder = document.createElement('div');
|
||||
nameLabelHolder.className = 'cssEditor-field-label-holder';
|
||||
var nameLabel = document.createElement('label');
|
||||
nameLabel.className = 'cssEditor-field-label';
|
||||
nameLabel.innerText = chrome.i18n.getMessage('styleNameLabel');
|
||||
nameLabelHolder.appendChild(nameLabel);
|
||||
editorHolderLeft.appendChild(nameLabelHolder);
|
||||
|
||||
var nameInputHolder = document.createElement('div');
|
||||
nameInputHolder.className = 'cssEditor-field-holder';
|
||||
var cssNameInput = document.createElement('input');
|
||||
cssNameInput.id = 'cssEditor-styleName';
|
||||
cssNameInput.type = 'text';
|
||||
nameInputHolder.appendChild(cssNameInput);
|
||||
editorHolderLeft.appendChild(nameInputHolder);
|
||||
|
||||
|
||||
var urlLabelHolder = document.createElement('div');
|
||||
urlLabelHolder.className = 'cssEditor-field-label-holder';
|
||||
var urlLabel = document.createElement('label');
|
||||
urlLabel.className = 'cssEditor-field-label';
|
||||
urlLabel.innerText = 'URL Regex'; // TODO addd link to regex tutorial
|
||||
var regexHelp = document.createElement('a');
|
||||
regexHelp.innerText = chrome.i18n.getMessage('howToWriteRegexLabel');
|
||||
regexHelp.setAttribute('href', 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions');
|
||||
urlLabelHolder.appendChild(urlLabel);
|
||||
urlLabelHolder.appendChild(regexHelp);
|
||||
editorHolderLeft.appendChild(urlLabelHolder);
|
||||
|
||||
var urlInputHolder = document.createElement('div');
|
||||
urlInputHolder.className = 'cssEditor-field-holder';
|
||||
var urlInput = document.createElement('input');
|
||||
urlInput.id = 'cssEditor-matchUrl';
|
||||
urlInput.type = 'text';
|
||||
urlInputHolder.appendChild(urlInput);
|
||||
editorHolderLeft.appendChild(urlInputHolder);
|
||||
|
||||
var contentLabelHolder = document.createElement('div');
|
||||
contentLabelHolder.className = 'cssEditor-field-label-holder';
|
||||
var contentLabel = document.createElement('label');
|
||||
contentLabel.className = 'cssEditor-field-label';
|
||||
contentLabel.innerText = 'CSS';
|
||||
contentLabelHolder.appendChild(contentLabel);
|
||||
editorHolderRight.appendChild(contentLabelHolder);
|
||||
|
||||
var contentInputHolder = document.createElement('div');
|
||||
contentInputHolder.className = 'cssEditor-field-holder';
|
||||
var contentInput = document.createElement('textarea');
|
||||
contentInput.id = 'cssEditor-styleContent';
|
||||
contentInput.setAttribute('data', 'language: css');
|
||||
contentInputHolder.appendChild(contentInput);
|
||||
editorHolderRight.appendChild(contentInputHolder);
|
||||
|
||||
editorHolder.appendChild(editorHolderLeft);
|
||||
editorHolder.appendChild(editorHolderRight);
|
||||
modalList.appendChild(editorHolder);
|
||||
|
||||
|
||||
|
||||
var saveButtonsHolder = document.createElement('div');
|
||||
|
||||
var removeCssButton = document.createElement('button');
|
||||
removeCssButton.id = 'cssEditor-removeStyle';
|
||||
removeCssButton.innerText = chrome.i18n.getMessage('removeStyle');
|
||||
removeCssButton.className = 'cssEditor-footer-button cssEditor-float-left cssEditor-cancel-button';
|
||||
removeCssButton.onclick = removeStyle;
|
||||
saveButtonsHolder.appendChild(removeCssButton);
|
||||
|
||||
var saveCssButton = document.createElement('button');
|
||||
saveCssButton.id = 'cssEditor-saveStyle';
|
||||
saveCssButton.innerText = chrome.i18n.getMessage('saveStyle');
|
||||
saveCssButton.className = 'cssEditor-footer-button cssEditor-float-right cssEditor-save-button';
|
||||
saveCssButton.onclick = saveStyle;
|
||||
saveButtonsHolder.appendChild(saveCssButton);
|
||||
|
||||
modalFooter.appendChild(saveButtonsHolder);
|
||||
|
||||
//////////////////////////
|
||||
|
||||
function createStyleList(allStylesTmp) {
|
||||
if (allStylesTmp && allStylesTmp.length > 0) {
|
||||
allStyles = allStyles.concat(allStylesTmp);
|
||||
}
|
||||
|
||||
while (existingStyles.hasChildNodes() && existingStyles.childElementCount > 1) {
|
||||
existingStyles.removeChild(existingStyles.lastChild);
|
||||
}
|
||||
|
||||
for (var i = 0; i < allStyles.length; i++) {
|
||||
var listItem = document.createElement('option');
|
||||
listItem.id = 'option_' + i;
|
||||
listItem.className = 'cssEditor-chapter-item';
|
||||
listItem.value = 'option_' + i;
|
||||
listItem.innerText = allStyles[i].title;
|
||||
if (currentStyle && (allStyles[i].title === currentStyle.title)) {
|
||||
listItem.selected = 'selected';
|
||||
}
|
||||
existingStyles.appendChild(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
function editCurrentStyle() {
|
||||
if (!currentStyle) {
|
||||
return;
|
||||
}
|
||||
|
||||
showStyleEditor();
|
||||
showRemoveStyle();
|
||||
showSaveStyle();
|
||||
|
||||
document.getElementById('cssEditor-styleName').value = currentStyle.title;
|
||||
document.getElementById('cssEditor-matchUrl').value = currentStyle.url;
|
||||
document.getElementById('cssEditor-styleContent').value = currentStyle.style;
|
||||
|
||||
}
|
||||
|
||||
function resetFields() {
|
||||
document.getElementById('cssEditor-styleName').value = '';
|
||||
document.getElementById('cssEditor-matchUrl').value = '';
|
||||
document.getElementById('cssEditor-styleContent').value = '';
|
||||
}
|
||||
|
||||
function hideStyleEditor() {
|
||||
document.getElementById('cssEditor-styleEditor').style.display = 'none';
|
||||
}
|
||||
|
||||
function showStyleEditor() {
|
||||
document.getElementById('cssEditor-styleEditor').style.display = 'flex';
|
||||
}
|
||||
|
||||
function showRemoveStyle() {
|
||||
document.getElementById('cssEditor-removeStyle').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideRemoveStyle() {
|
||||
document.getElementById('cssEditor-removeStyle').style.display = 'none';
|
||||
}
|
||||
|
||||
function showSaveStyle() {
|
||||
document.getElementById('cssEditor-saveStyle').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideSaveStyle() {
|
||||
document.getElementById('cssEditor-saveStyle').style.display = 'none';
|
||||
}
|
||||
|
||||
function saveStyle() {
|
||||
var isRegexValid = checkRegex();
|
||||
if (!isRegexValid) {
|
||||
alert(chrome.i18n.getMessage('invalidRegex'));
|
||||
return;
|
||||
}
|
||||
var tmpValue = {
|
||||
title: document.getElementById('cssEditor-styleName').value,
|
||||
url: document.getElementById('cssEditor-matchUrl').value,
|
||||
style: document.getElementById('cssEditor-styleContent').value
|
||||
}
|
||||
if (currentStyle === null) {
|
||||
allStyles.push(tmpValue);
|
||||
currentStyle = tmpValue;
|
||||
currentStyleIndex = allStyles.length - 1;
|
||||
} else {
|
||||
currentStyle = tmpValue;
|
||||
allStyles[currentStyleIndex] = currentStyle;
|
||||
}
|
||||
setStyles(allStyles);
|
||||
createStyleList();
|
||||
showRemoveStyle();
|
||||
alert(chrome.i18n.getMessage('styleSaved'));
|
||||
}
|
||||
|
||||
function checkRegex() {
|
||||
var regexContent = document.getElementById('cssEditor-matchUrl').value;
|
||||
var isValid = true;
|
||||
try {
|
||||
new RegExp(regexContent);
|
||||
} catch(e) {
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function removeStyle() {
|
||||
if (confirm(chrome.i18n.getMessage('confirmDeleteStyle')) == true) {
|
||||
allStyles.splice(currentStyleIndex, 1);
|
||||
setStyles(allStyles);
|
||||
hideSaveStyle();
|
||||
hideRemoveStyle();
|
||||
hideStyleEditor();
|
||||
createStyleList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////
|
||||
|
||||
var modal = document.createElement('div');
|
||||
modal.id = 'cssEditor-Modal';
|
||||
|
||||
modalContent.appendChild(modalHeader);
|
||||
modalContent.appendChild(modalList);
|
||||
modalContent.appendChild(modalFooter);
|
||||
modal.appendChild(modalContent);
|
||||
|
||||
body.appendChild(modal);
|
||||
|
||||
modal.style.display = "none";
|
||||
modal.style.position = 'fixed';
|
||||
modal.style.zIndex = '1';
|
||||
modal.style.left = '0';
|
||||
modal.style.top = '0';
|
||||
modal.style.width = '100%';
|
||||
modal.style.height = '100%';
|
||||
modal.style.overflow = 'auto';
|
||||
modal.style.backgroundColor = 'rgba(210, 210, 210, 1)';
|
||||
|
||||
modalContent.style.zIndex = '2';
|
||||
modalContent.style.backgroundColor = '#fff';
|
||||
modalContent.style.margin = '5% auto';
|
||||
modalContent.style.padding = '0';
|
||||
modalContent.style.width = '70%';
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
modal.style.display = "block";
|
||||
|
||||
document.onkeydown = function(evt) {
|
||||
evt = evt || window.event;
|
||||
if (evt.keyCode == 27) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
function closeModal() {
|
||||
for (var i=0; i<document.styleSheets.length; i++) {
|
||||
document.styleSheets.item(i).disabled = false;
|
||||
}
|
||||
modal.style.display = "none";
|
||||
modalContent.parentNode.removeChild(modalContent);
|
||||
modal.parentNode.removeChild(modal);
|
||||
}
|
||||
|
||||
function saveChanges() {
|
||||
var newChapters = [];
|
||||
var newEbookTitle = ebookTilte.value;
|
||||
if (newEbookTitle.trim() === '') {
|
||||
newEbookTitle = 'eBook';
|
||||
}
|
||||
|
||||
try {
|
||||
var tmpChaptersList = document.getElementsByClassName('cssEditor-chapter-item');
|
||||
if (!tmpChaptersList || !allPagesRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < tmpChaptersList.length; i++) {
|
||||
var listIndex = Number(tmpChaptersList[i].id.replace('li', ''));
|
||||
if (allPagesRef[listIndex].removed === false) {
|
||||
newChapters.push(allPagesRef[listIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
saveEbookTitle(newEbookTitle);
|
||||
saveEbookPages(newChapters);
|
||||
return newChapters;
|
||||
} catch (e) {
|
||||
console.log('Error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
|
||||
getStyles(createStyleList);
|
||||
}
|
||||
|
|
@ -1,18 +1,50 @@
|
|||
|
||||
// Used to replace <img> src links that don't have a file extension
|
||||
// If the image src doesn't have a file type:
|
||||
// 1. Create a dummy link
|
||||
// 2. Detect image type from the binary data & create new links
|
||||
// 3. Replace all the dummy links in tmpGlobalContent with the new links
|
||||
var tmpGlobalContent = null
|
||||
|
||||
var allImages = [];
|
||||
var extractedImages = [];
|
||||
var maxNrOfElements = 20000;
|
||||
var allowedTags = [
|
||||
'address', 'article', 'aside', 'footer', 'header', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'hgroup', 'nav', 'section', 'dd', 'div', 'dl', 'dt', 'figcaption', 'figure', 'hr', 'li',
|
||||
'main', 'ol', 'p', 'pre', 'ul', 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data',
|
||||
'dfn', 'em', 'i', 'img', 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span',
|
||||
'dfn', 'em', 'i', 'img', 'kbd', 'mark', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'small', 'span',
|
||||
'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr', 'del', 'ins', 'caption', 'col', 'colgroup',
|
||||
'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr',
|
||||
'math', 'maction', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot',
|
||||
'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'msgroup', 'mlongdiv', 'mscarries',
|
||||
'mscarry', 'mstack'
|
||||
];
|
||||
'mscarry', 'mstack', 'semantics'
|
||||
// TODO ?
|
||||
// ,'form', 'button'
|
||||
|
||||
// TODO svg support ?
|
||||
// , 'svg', 'g', 'path', 'line', 'circle', 'text'
|
||||
];
|
||||
// const svgTags = ['svg', 'g', 'path', 'line', 'circle', 'text']
|
||||
var mathMLTags = [
|
||||
'math', 'maction', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot',
|
||||
'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'msgroup', 'mlongdiv', 'mscarries',
|
||||
'mscarry', 'mstack', 'semantics'
|
||||
]
|
||||
var cssClassesToTmpIds = {};
|
||||
var tmpIdsToNewCss = {};
|
||||
var tmpIdsToNewCssSTRING = {};
|
||||
|
||||
// src: https://idpf.github.io/a11y-guidelines/content/style/reference.html
|
||||
var supportedCss = [
|
||||
'background-color',
|
||||
'border',
|
||||
'color',
|
||||
'font',
|
||||
'line-height',
|
||||
'list-style',
|
||||
'padding',
|
||||
'text-align',
|
||||
];
|
||||
//////
|
||||
|
||||
function getImageSrc(srcTxt) {
|
||||
|
|
@ -23,16 +55,25 @@ function getImageSrc(srcTxt) {
|
|||
if (srcTxt === '') {
|
||||
return '';
|
||||
}
|
||||
var isB64Img = isBase64Img(srcTxt);
|
||||
var fileExtension = getFileExtension(srcTxt);
|
||||
var newImgFileName = 'img-' + (Math.floor(Math.random()*1000000*Math.random()*100000)) + '.' + fileExtension;
|
||||
|
||||
// TODO move
|
||||
srcTxt = srcTxt.replace(/&/g, '&')
|
||||
|
||||
// TODO - convert <imgs> with svg sources to jpeg OR add support for svg
|
||||
|
||||
let fileExtension = getFileExtension(srcTxt);
|
||||
if (fileExtension === '') {
|
||||
fileExtension = "TODO-EXTRACT"
|
||||
}
|
||||
let newImgFileName = 'img-' + generateRandomNumber(true) + '.' + fileExtension;
|
||||
|
||||
let isB64Img = isBase64Img(srcTxt);
|
||||
if (isB64Img) {
|
||||
extractedImages.push({
|
||||
filename: newImgFileName, // TODO name
|
||||
data: getBase64ImgData(srcTxt)
|
||||
});
|
||||
} else {
|
||||
} else {
|
||||
allImages.push({
|
||||
originalUrl: getImgDownloadUrl(srcTxt),
|
||||
filename: newImgFileName, // TODO name
|
||||
|
|
@ -42,164 +83,163 @@ function getImageSrc(srcTxt) {
|
|||
return '../images/' + newImgFileName;
|
||||
}
|
||||
|
||||
function formatPreCodeElements($jQueryElement) {
|
||||
$jQueryElement.find('pre').each(function (i, pre) {
|
||||
$(pre).replaceWith('<pre>' + pre.innerText + '</pre>');
|
||||
});
|
||||
$jQueryElement.find('code').each(function (i, pre) {
|
||||
$(pre).replaceWith('<code>' + pre.innerText + '</code>');
|
||||
});
|
||||
}
|
||||
|
||||
// tested
|
||||
function extractMathMl($htmlObject) {
|
||||
$htmlObject.find('span[id^="MathJax-Element-"]').each(function (i, el) {
|
||||
$(el).replaceWith('<span>' + el.getAttribute('data-mathml') + '</span>');
|
||||
});
|
||||
}
|
||||
|
||||
// tested
|
||||
function extractCanvasToImg($htmlObject) {
|
||||
$htmlObject.find('canvas').each(function (index, elem) {
|
||||
var tmpXP = getXPath(elem);
|
||||
tmpXP = tmpXP.replace(/^\/div\[1\]/m, '/html[1]/body[1]');
|
||||
var docEl = lookupElementByXPath(tmpXP);
|
||||
var jpegUrl = docEl.toDataURL('image/png');
|
||||
$(elem).replaceWith('<img src="' + jpegUrl + '" alt=""></img>');
|
||||
try {
|
||||
let imgUrl = docEl.toDataURL('image/jpeg');
|
||||
$(elem).replaceWith('<img src="' + imgUrl + '" alt=""></img>');
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// tested
|
||||
function extractSvgToImg($htmlObject) {
|
||||
var serializer = new XMLSerializer();
|
||||
let serializer = new XMLSerializer();
|
||||
$htmlObject.find('svg').each(function (index, elem) {
|
||||
var svgXml = serializer.serializeToString(elem);
|
||||
var imgSrc = 'data:image/svg+xml;base64,' + window.btoa(svgXml);
|
||||
$(elem).replaceWith('<img src="' + imgSrc + '">' + '</img>');
|
||||
// add width & height because the result image was too big
|
||||
let bbox = elem.getBoundingClientRect()
|
||||
let newWidth = bbox.width
|
||||
let newHeight = bbox.height
|
||||
let svgXml = serializer.serializeToString(elem);
|
||||
let imgSrc = 'data:image/svg+xml;base64,' + window.btoa(svgXml);
|
||||
$(elem).replaceWith('<img src="' + imgSrc + '" width="'+newWidth+'" height="'+newHeight+'">' + '</img>');
|
||||
});
|
||||
}
|
||||
|
||||
function preProcess($htmlObject) {
|
||||
extractMathMl($htmlObject);
|
||||
extractCanvasToImg($htmlObject);
|
||||
extractSvgToImg($htmlObject);
|
||||
$htmlObject.find('script, style, noscript, iframe').remove();
|
||||
$htmlObject.find('*:empty').not('img').remove();
|
||||
formatPreCodeElements($htmlObject);
|
||||
}
|
||||
|
||||
function force($content, withError) {
|
||||
try {
|
||||
var tagOpen = '@@@' + generateRandomTag();
|
||||
var tagClose = '###' + generateRandomTag();
|
||||
var startEl = '<object>';
|
||||
var endEl = '</object>';
|
||||
|
||||
if (withError) {
|
||||
$content = $($content);
|
||||
preProcess($content);
|
||||
// replaces all iframes by divs with the same innerHTML content
|
||||
function extractIFrames() {
|
||||
let allIframes = document.getElementsByTagName('iframe')
|
||||
let changeIFrames = []
|
||||
let newDivs = []
|
||||
for (let iFrame of allIframes) {
|
||||
if (!iFrame.contentDocument || !iFrame.contentDocument.body) {
|
||||
continue
|
||||
}
|
||||
|
||||
$content.find('img').each(function (index, elem) {
|
||||
var imgSrc = getImageSrc($(elem).attr('src'));
|
||||
if (imgSrc === '') {
|
||||
$(elem).replaceWith('');
|
||||
} else {
|
||||
$(elem).replaceWith(startEl + tagOpen + 'img src="' + imgSrc + '"' + tagClose + tagOpen + '/img' + tagClose + endEl);
|
||||
}
|
||||
});
|
||||
|
||||
$content.find('a').each(function (index, elem) {
|
||||
var aHref = getHref($(elem).attr('href'));
|
||||
if (aHref === '') {
|
||||
$(elem).replaceWith('');
|
||||
} else {
|
||||
$(elem).replaceWith(startEl + tagOpen + 'a href="' + aHref + '"' + tagClose + $(elem).html() + tagOpen + '/a' + tagClose + endEl);
|
||||
}
|
||||
});
|
||||
|
||||
all($content);
|
||||
|
||||
function all($startElement) {
|
||||
var tagName = $startElement.get(0).tagName.toLowerCase();
|
||||
if (allowedTags.indexOf(tagName) >= 0) {
|
||||
var children = $startElement.children();
|
||||
var childrenLen = children.length;
|
||||
while (childrenLen--) {
|
||||
all($(children[childrenLen]));
|
||||
}
|
||||
$startElement.replaceWith(startEl + tagOpen + tagName + tagClose + $startElement.html() + tagOpen + '/' + tagName + tagClose + endEl);
|
||||
}
|
||||
}
|
||||
|
||||
var contentString = $content.text();
|
||||
|
||||
var tagOpenRegex = new RegExp(tagOpen, 'gi');
|
||||
var tagCloseRegex = new RegExp(tagClose, 'gi');
|
||||
contentString = contentString.replace(tagOpenRegex, '<');
|
||||
contentString = contentString.replace(tagCloseRegex, '>');
|
||||
contentString = contentString.replace(/ /gi, ' ');
|
||||
|
||||
return contentString;
|
||||
} catch (e) {
|
||||
console.log('Error:', e);
|
||||
return '';
|
||||
let bodyContent = iFrame.contentDocument.body.innerHTML
|
||||
let bbox = iFrame.getBoundingClientRect()
|
||||
let newDiv = document.createElement('div')
|
||||
newDiv.style.width = bbox.width
|
||||
newDiv.style.height = bbox.height
|
||||
newDiv.innerHTML = bodyContent
|
||||
changeIFrames.push(iFrame)
|
||||
newDivs.push(newDiv)
|
||||
}
|
||||
for (let i = 0; i < newDivs.length; i++) {
|
||||
let newDiv = newDivs[i]
|
||||
let iFrame = changeIFrames[i]
|
||||
let iframeParent = iFrame.parentNode
|
||||
iframeParent.replaceChild(newDiv, iFrame)
|
||||
}
|
||||
}
|
||||
|
||||
function sanitize(rawContentString) {
|
||||
function preProcess($htmlObject) {
|
||||
// TODO
|
||||
// $htmlObject.find('script, style, noscript, iframe').remove();
|
||||
// $('body').find('script, style, noscript, iframe').remove()
|
||||
// $('body').find('script, style, noscript, iframe').contents().remove()
|
||||
// $('body').find('iframe').remove()
|
||||
// $('body').find('*:empty').not('img').not('br').not('hr').remove();
|
||||
// formatPreCodeElements($('body'));
|
||||
|
||||
extractMathMl($htmlObject);
|
||||
extractCanvasToImg($htmlObject);
|
||||
extractSvgToImg($htmlObject);
|
||||
}
|
||||
|
||||
function parseHTML(rawContentString) {
|
||||
allImages = [];
|
||||
extractedImages = [];
|
||||
var srcTxt = '';
|
||||
var dirty = null;
|
||||
let results = '';
|
||||
let lastFragment = '';
|
||||
let lastTag = '';
|
||||
|
||||
try {
|
||||
var wdirty = $.parseHTML(rawContentString);
|
||||
$wdirty = $(wdirty);
|
||||
|
||||
preProcess($wdirty);
|
||||
|
||||
if ($('*').length > maxNrOfElements) {
|
||||
return force($wdirty, false);
|
||||
}
|
||||
|
||||
dirty = '<div>' + $wdirty.html() + '</div>';
|
||||
|
||||
var results = '';
|
||||
var lastFragment = '';
|
||||
var lastTag = '';
|
||||
|
||||
HTMLParser(dirty, {
|
||||
HTMLParser(rawContentString, {
|
||||
start: function(tag, attrs, unary) {
|
||||
lastTag = tag;
|
||||
if (allowedTags.indexOf(tag) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tattrs = null;
|
||||
if (tag === 'img') {
|
||||
tattrs = attrs.filter(function(attr) {
|
||||
return attr.name === 'src';
|
||||
}).map(function(attr) {
|
||||
return getImageSrc(attr.value);
|
||||
});
|
||||
lastFragment = tattrs.length === 0 ? '<img></img>' : '<img src="' + tattrs[0] + '" alt=""></img>';
|
||||
let tmpAttrsTxt = '';
|
||||
let tmpSrc = ''
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i].name === 'src') {
|
||||
tmpSrc = getImageSrc(attrs[i].value)
|
||||
tmpAttrsTxt += ' src="' + tmpSrc + '"';
|
||||
} else if (attrs[i].name === 'data-class') {
|
||||
tmpAttrsTxt += ' class="' + attrs[i].value + '"';
|
||||
} else if (attrs[i].name === 'width') {
|
||||
// used when converting svg to img - the result image was too big
|
||||
tmpAttrsTxt += ' width="' + attrs[i].value + '"';
|
||||
} else if (attrs[i].name === 'height') {
|
||||
// used when converting svg to img - the result image was too big
|
||||
tmpAttrsTxt += ' height="' + attrs[i].value + '"';
|
||||
}
|
||||
}
|
||||
if (tmpSrc === '') {
|
||||
// ignore imgs without source
|
||||
lastFragment = ''
|
||||
} else {
|
||||
lastFragment = tmpAttrsTxt.length === 0 ? '<img></img>' : '<img' + tmpAttrsTxt + ' alt=""></img>';
|
||||
}
|
||||
} else if (tag === 'a') {
|
||||
tattrs = attrs.filter(function(attr) {
|
||||
return attr.name === 'href';
|
||||
}).map(function(attr) {
|
||||
return getHref(attr.value);
|
||||
});
|
||||
lastFragment = tattrs.length === 0 ? '<a>' : '<a href="' + tattrs[0] + '">';
|
||||
let tmpAttrsTxt = '';
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i].name === 'href') {
|
||||
tmpAttrsTxt += ' href="' + getHref(attrs[i].value) + '"';
|
||||
} else if (attrs[i].name === 'data-class') {
|
||||
tmpAttrsTxt += ' class="' + attrs[i].value + '"';
|
||||
}
|
||||
}
|
||||
lastFragment = tmpAttrsTxt.length === 0 ? '<a>' : '<a' + tmpAttrsTxt + '>';
|
||||
} else if (tag === 'br' || tag === 'hr') {
|
||||
let tmpAttrsTxt = '';
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i].name === 'data-class') {
|
||||
tmpAttrsTxt += ' class="' + attrs[i].value + '"';
|
||||
}
|
||||
}
|
||||
lastFragment = '<' + tag + tmpAttrsTxt + '></' + tag + '>';
|
||||
} else if (tag === 'math') {
|
||||
let tmpAttrsTxt = '';
|
||||
tmpAttrsTxt += ' xmlns="http://www.w3.org/1998/Math/MathML"';
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i].name === 'alttext') {
|
||||
tmpAttrsTxt += ' alttext="' + attrs[i].value + '"';
|
||||
}
|
||||
}
|
||||
lastFragment = '<' + tag + tmpAttrsTxt + '>';
|
||||
} else {
|
||||
lastFragment = '<' + tag + '>';
|
||||
let tmpAttrsTxt = '';
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
if (attrs[i].name === 'data-class') {
|
||||
tmpAttrsTxt += ' class="' + attrs[i].value + '"';
|
||||
}
|
||||
}
|
||||
lastFragment = '<' + tag + tmpAttrsTxt + '>';
|
||||
}
|
||||
|
||||
results += lastFragment;
|
||||
lastFragment = '';
|
||||
},
|
||||
end: function(tag) {
|
||||
if (allowedTags.indexOf(tag) < 0 || tag === 'img') {
|
||||
if (allowedTags.indexOf(tag) < 0 || tag === 'img' || tag === 'br' || tag === 'hr') {
|
||||
return;
|
||||
}
|
||||
|
||||
results += "</" + tag + ">\n";
|
||||
results += "</" + tag + ">";
|
||||
},
|
||||
chars: function(text) {
|
||||
if (lastTag !== '' && allowedTags.indexOf(lastTag) < 0) {
|
||||
|
|
@ -212,50 +252,42 @@ function sanitize(rawContentString) {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO - (re)move
|
||||
results = results.replace(/ /gi, ' ');
|
||||
|
||||
return results;
|
||||
|
||||
} catch (e) {
|
||||
console.log('Error:', e);
|
||||
return force(dirty, true);
|
||||
return 'Error: ' + e //+" " + force($(rawContentString))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getContent(htmlContent) {
|
||||
try {
|
||||
var tmp = document.createElement('div');
|
||||
// TODO - move; called multiple times on selection
|
||||
preProcess($('body'))
|
||||
let tmp = document.createElement('div');
|
||||
tmp.appendChild(htmlContent.cloneNode(true));
|
||||
var dirty = '<div>' + tmp.innerHTML + '</div>';
|
||||
return sanitize(dirty);
|
||||
let tmpHtml = '<div>' + tmp.innerHTML + '</div>';
|
||||
return parseHTML(tmpHtml);
|
||||
} catch (e) {
|
||||
console.log('Error:', e);
|
||||
return '';
|
||||
return htmlContent;
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
function getPageUrl(url) {
|
||||
return url.toLowerCase().replace(/\s+/g,'_').replace(/[^a-z0-9_]/g,'') + Math.floor(Math.random() * 10000) + '.xhtml';
|
||||
}
|
||||
|
||||
function getPageTitle(title) {
|
||||
if (title.trim().length === 0) {
|
||||
return 'ebook';
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
function getSelectedNodes() {
|
||||
// if (document.selection) {
|
||||
// return document.selection.createRange().parentElement();
|
||||
// return document.selection.createRange();
|
||||
// }
|
||||
var selection = window.getSelection();
|
||||
var docfrag = [];
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
let selection = window.getSelection();
|
||||
let docfrag = [];
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
docfrag.push(selection.getRangeAt(i).cloneContents());
|
||||
}
|
||||
return docfrag;
|
||||
|
|
@ -263,66 +295,174 @@ function getSelectedNodes() {
|
|||
|
||||
/////
|
||||
|
||||
function extractCss(includeStyle, appliedStyles) {
|
||||
if (includeStyle) {
|
||||
$('body').find('*').each((i, pre) => {
|
||||
let $pre = $(pre);
|
||||
|
||||
if (allowedTags.indexOf(pre.tagName.toLowerCase()) < 0) return;
|
||||
if (mathMLTags.indexOf(pre.tagName.toLowerCase()) > -1) return;
|
||||
|
||||
if (!$pre.is(':visible')) {
|
||||
$pre.replaceWith('');
|
||||
} else {
|
||||
if (pre.tagName.toLowerCase() === 'svg') return;
|
||||
|
||||
let classNames = pre.getAttribute('class');
|
||||
if (!classNames) {
|
||||
classNames = pre.getAttribute('id');
|
||||
if (!classNames) {
|
||||
classNames = pre.tagName + '-' + generateRandomNumber();
|
||||
}
|
||||
}
|
||||
let tmpName = cssClassesToTmpIds[classNames];
|
||||
let tmpNewCss = tmpIdsToNewCss[tmpName];
|
||||
if (!tmpName) {
|
||||
// TODO - collision between class names when multiple pages
|
||||
tmpName = generateRandomTag(2) + i
|
||||
cssClassesToTmpIds[classNames] = tmpName;
|
||||
}
|
||||
if (!tmpNewCss) {
|
||||
tmpNewCss = {};
|
||||
|
||||
for (let cssTagName of supportedCss) {
|
||||
let cssValue = $pre.css(cssTagName);
|
||||
if (cssValue && cssValue.length > 0) {
|
||||
tmpNewCss[cssTagName] = cssValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse CSS - if the same css code was generated for another element, reuse it's class name
|
||||
|
||||
let tcss = JSON.stringify(tmpNewCss)
|
||||
let found = false
|
||||
|
||||
if (Object.keys(tmpIdsToNewCssSTRING).length === 0) {
|
||||
tmpIdsToNewCssSTRING[tmpName] = tcss;
|
||||
tmpIdsToNewCss[tmpName] = tmpNewCss;
|
||||
} else {
|
||||
for (const key in tmpIdsToNewCssSTRING) {
|
||||
if (tmpIdsToNewCssSTRING[key] === tcss) {
|
||||
tmpName = key
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
tmpIdsToNewCssSTRING[tmpName] = tcss;
|
||||
tmpIdsToNewCss[tmpName] = tmpNewCss;
|
||||
}
|
||||
}
|
||||
}
|
||||
pre.setAttribute('data-class', tmpName);
|
||||
}
|
||||
});
|
||||
return jsonToCss(tmpIdsToNewCss);
|
||||
} else {
|
||||
// remove hidden elements when style is not included
|
||||
$('body').find('*').each((i, pre) => {
|
||||
let $pre = $(pre)
|
||||
if (!$pre.is(':visible')) {
|
||||
$pre.replaceWith('')
|
||||
}
|
||||
})
|
||||
let mergedCss = '';
|
||||
if (appliedStyles && appliedStyles.length > 0) {
|
||||
for (let i = 0; i < appliedStyles.length; i++) {
|
||||
mergedCss += appliedStyles[i].style;
|
||||
}
|
||||
return mergedCss;
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
function deferredAddZip(url, filename) {
|
||||
var deferred = $.Deferred();
|
||||
JSZipUtils.getBinaryContent(url, function(err, data) {
|
||||
let deferred = $.Deferred();
|
||||
JSZipUtils.getBinaryContent(url, function(err, data) {
|
||||
if (err) {
|
||||
// deferred.reject(err); TODO
|
||||
console.log('Error:', err);
|
||||
deferred.resolve();
|
||||
} else {
|
||||
// TODO - move to utils.js
|
||||
if (filename.endsWith("TODO-EXTRACT")) {
|
||||
let oldFilename = filename
|
||||
let arr = (new Uint8Array(data)).subarray(0, 4);
|
||||
let header = "";
|
||||
for(let i = 0; i < arr.length; i++) {
|
||||
header += arr[i].toString(16);
|
||||
}
|
||||
if (header.startsWith("89504e47")) {
|
||||
filename = filename.replace("TODO-EXTRACT", "png")
|
||||
} else if (header.startsWith("47494638")) {
|
||||
filename = filename.replace("TODO-EXTRACT", "gif")
|
||||
} else if (header.startsWith("ffd8ff")) {
|
||||
filename = filename.replace("TODO-EXTRACT", "jpg")
|
||||
} else {
|
||||
// ERROR
|
||||
console.log("Error! Unable to extract the image type!");
|
||||
deferred.resolve();
|
||||
}
|
||||
tmpGlobalContent = tmpGlobalContent.replace(oldFilename, filename)
|
||||
}
|
||||
|
||||
extractedImages.push({
|
||||
filename: filename,
|
||||
// TODO - must be JSON serializable
|
||||
data: base64ArrayBuffer(data)
|
||||
});
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred;
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
||||
var imgsPromises = [];
|
||||
var result = {};
|
||||
var pageSrc = '';
|
||||
var tmpContent = '';
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
let imgsPromises = [];
|
||||
let result = {};
|
||||
let pageSrc = '';
|
||||
let tmpContent = '';
|
||||
let styleFile = null;
|
||||
|
||||
extractIFrames()
|
||||
|
||||
if (request.type === 'extract-page') {
|
||||
styleFile = extractCss(request.includeStyle, request.appliedStyles)
|
||||
pageSrc = document.getElementsByTagName('body')[0];
|
||||
tmpContent = getContent(pageSrc);
|
||||
} else if (request.type === 'extract-selection') {
|
||||
styleFile = extractCss(request.includeStyle, request.appliedStyles)
|
||||
pageSrc = getSelectedNodes();
|
||||
pageSrc.forEach(function (page) {
|
||||
pageSrc.forEach((page) => {
|
||||
tmpContent += getContent(page);
|
||||
});
|
||||
} else if (request.type === 'echo') {
|
||||
sendResponse({
|
||||
echo: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (tmpContent.trim() === '') {
|
||||
sendResponse('');
|
||||
return;
|
||||
}
|
||||
tmpGlobalContent = tmpContent
|
||||
|
||||
allImages.forEach(function (tmpImg) {
|
||||
allImages.forEach((tmpImg) => {
|
||||
imgsPromises.push(deferredAddZip(tmpImg.originalUrl, tmpImg.filename));
|
||||
});
|
||||
|
||||
$.when.apply($, imgsPromises).done(function() {
|
||||
var tmpTitle = getPageTitle(document.title);
|
||||
$.when.apply($, imgsPromises).done(() => {
|
||||
let tmpTitle = getPageTitle(document.title);
|
||||
result = {
|
||||
url: getPageUrl(tmpTitle),
|
||||
title: tmpTitle,
|
||||
baseUrl: getCurrentUrl(),
|
||||
styleFileContent: styleFile,
|
||||
styleFileName: 'style' + generateRandomNumber() + '.css',
|
||||
images: extractedImages,
|
||||
content: tmpContent
|
||||
content: tmpGlobalContent
|
||||
};
|
||||
sendResponse(result);
|
||||
}).fail(function(e) {
|
||||
}).fail((e) => {
|
||||
console.log('Error:', e);
|
||||
sendResponse(null)
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
/* FileSaver.js
|
||||
* A saveAs() FileSaver implementation.
|
||||
* 1.3.2
|
||||
* 2016-06-16 18:25:19
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* License: MIT
|
||||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self */
|
||||
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
|
||||
|
||||
var saveAs = saveAs || (function(view) {
|
||||
"use strict";
|
||||
// IE <10 is explicitly unsupported
|
||||
if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
|
||||
return;
|
||||
}
|
||||
var
|
||||
doc = view.document
|
||||
// only get URL when necessary in case Blob.js hasn't overridden it yet
|
||||
, get_URL = function() {
|
||||
return view.URL || view.webkitURL || view;
|
||||
}
|
||||
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||||
, can_use_save_link = "download" in save_link
|
||||
, click = function(node) {
|
||||
var event = new MouseEvent("click");
|
||||
node.dispatchEvent(event);
|
||||
}
|
||||
, is_safari = /constructor/i.test(view.HTMLElement)
|
||||
, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
|
||||
, throw_outside = function(ex) {
|
||||
(view.setImmediate || view.setTimeout)(function() {
|
||||
throw ex;
|
||||
}, 0);
|
||||
}
|
||||
, force_saveable_type = "application/octet-stream"
|
||||
// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
|
||||
, arbitrary_revoke_timeout = 1000 * 40 // in ms
|
||||
, revoke = function(file) {
|
||||
var revoker = function() {
|
||||
if (typeof file === "string") { // file is an object URL
|
||||
get_URL().revokeObjectURL(file);
|
||||
} else { // file is a File
|
||||
file.remove();
|
||||
}
|
||||
};
|
||||
setTimeout(revoker, arbitrary_revoke_timeout);
|
||||
}
|
||||
, dispatch = function(filesaver, event_types, event) {
|
||||
event_types = [].concat(event_types);
|
||||
var i = event_types.length;
|
||||
while (i--) {
|
||||
var listener = filesaver["on" + event_types[i]];
|
||||
if (typeof listener === "function") {
|
||||
try {
|
||||
listener.call(filesaver, event || filesaver);
|
||||
} catch (ex) {
|
||||
throw_outside(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
, auto_bom = function(blob) {
|
||||
// prepend BOM for UTF-8 XML and text/* types (including HTML)
|
||||
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
|
||||
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||
return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
, FileSaver = function(blob, name, no_auto_bom) {
|
||||
if (!no_auto_bom) {
|
||||
blob = auto_bom(blob);
|
||||
}
|
||||
// First try a.download, then web filesystem, then object URLs
|
||||
var
|
||||
filesaver = this
|
||||
, type = blob.type
|
||||
, force = type === force_saveable_type
|
||||
, object_url
|
||||
, dispatch_all = function() {
|
||||
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
||||
}
|
||||
// on any filesys errors revert to saving with object URLs
|
||||
, fs_error = function() {
|
||||
if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
|
||||
// Safari doesn't allow downloading of blob urls
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
|
||||
var popup = view.open(url, '_blank');
|
||||
if(!popup) view.location.href = url;
|
||||
url=undefined; // release reference before dispatching
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
return;
|
||||
}
|
||||
// don't create more object URLs than needed
|
||||
if (!object_url) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
}
|
||||
if (force) {
|
||||
view.location.href = object_url;
|
||||
} else {
|
||||
var opened = view.open(object_url, "_blank");
|
||||
if (!opened) {
|
||||
// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
|
||||
view.location.href = object_url;
|
||||
}
|
||||
}
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
}
|
||||
;
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
|
||||
if (can_use_save_link) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
setTimeout(function() {
|
||||
save_link.href = object_url;
|
||||
save_link.download = name;
|
||||
click(save_link);
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fs_error();
|
||||
}
|
||||
, FS_proto = FileSaver.prototype
|
||||
, saveAs = function(blob, name, no_auto_bom) {
|
||||
return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
|
||||
}
|
||||
;
|
||||
// IE 10+ (native saveAs)
|
||||
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
|
||||
return function(blob, name, no_auto_bom) {
|
||||
name = name || blob.name || "download";
|
||||
|
||||
if (!no_auto_bom) {
|
||||
blob = auto_bom(blob);
|
||||
}
|
||||
return navigator.msSaveOrOpenBlob(blob, name);
|
||||
};
|
||||
}
|
||||
|
||||
FS_proto.abort = function(){};
|
||||
FS_proto.readyState = FS_proto.INIT = 0;
|
||||
FS_proto.WRITING = 1;
|
||||
FS_proto.DONE = 2;
|
||||
|
||||
FS_proto.error =
|
||||
FS_proto.onwritestart =
|
||||
FS_proto.onprogress =
|
||||
FS_proto.onwrite =
|
||||
FS_proto.onabort =
|
||||
FS_proto.onerror =
|
||||
FS_proto.onwriteend =
|
||||
null;
|
||||
|
||||
return saveAs;
|
||||
}(
|
||||
typeof self !== "undefined" && self
|
||||
|| typeof window !== "undefined" && window
|
||||
|| this.content
|
||||
));
|
||||
// `self` is undefined in Firefox for Android content script context
|
||||
// while `this` is nsIContentFrameMessageManager
|
||||
// with an attribute `content` that corresponds to the window
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports.saveAs = saveAs;
|
||||
} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) {
|
||||
define([], function() {
|
||||
return saveAs;
|
||||
});
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 656 B |
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 868 B |
297
web-extension/libs/cssjson.js
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/**
|
||||
* CSS-JSON Converter for JavaScript
|
||||
* Converts CSS to JSON and back.
|
||||
* Version 2.1
|
||||
*
|
||||
* Released under the MIT license.
|
||||
*
|
||||
* Copyright (c) 2013 Aram Kocharyan, http://aramk.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var CSSJSON = new function () {
|
||||
|
||||
var base = this;
|
||||
|
||||
base.init = function () {
|
||||
// String functions
|
||||
String.prototype.trim = function () {
|
||||
return this.replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
|
||||
String.prototype.repeat = function (n) {
|
||||
return new Array(1 + n).join(this);
|
||||
};
|
||||
};
|
||||
base.init();
|
||||
|
||||
var selX = /([^\s\;\{\}][^\;\{\}]*)\{/g;
|
||||
var endX = /\}/g;
|
||||
var lineX = /([^\;\{\}]*)\;/g;
|
||||
var commentX = /\/\*[\s\S]*?\*\//g;
|
||||
var lineAttrX = /([^\:]+):([^\;]*);/;
|
||||
|
||||
// This is used, a concatenation of all above. We use alternation to
|
||||
// capture.
|
||||
var altX = /(\/\*[\s\S]*?\*\/)|([^\s\;\{\}][^\;\{\}]*(?=\{))|(\})|([^\;\{\}]+\;(?!\s*\*\/))/gmi;
|
||||
|
||||
// Capture groups
|
||||
var capComment = 1;
|
||||
var capSelector = 2;
|
||||
var capEnd = 3;
|
||||
var capAttr = 4;
|
||||
|
||||
var isEmpty = function (x) {
|
||||
return typeof x == 'undefined' || x.length == 0 || x == null;
|
||||
};
|
||||
|
||||
var isCssJson = function (node) {
|
||||
return !isEmpty(node) ? (node.attributes && node.children) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input is css string and current pos, returns JSON object
|
||||
*
|
||||
* @param cssString
|
||||
* The CSS string.
|
||||
* @param args
|
||||
* An optional argument object. ordered: Whether order of
|
||||
* comments and other nodes should be kept in the output. This
|
||||
* will return an object where all the keys are numbers and the
|
||||
* values are objects containing "name" and "value" keys for each
|
||||
* node. comments: Whether to capture comments. split: Whether to
|
||||
* split each comma separated list of selectors.
|
||||
*/
|
||||
base.toJSON = function (cssString, args) {
|
||||
var node = {
|
||||
children: {},
|
||||
attributes: {}
|
||||
};
|
||||
var match = null;
|
||||
var count = 0;
|
||||
|
||||
if (typeof args == 'undefined') {
|
||||
var args = {
|
||||
ordered: false,
|
||||
comments: false,
|
||||
stripComments: false,
|
||||
split: false
|
||||
};
|
||||
}
|
||||
if (args.stripComments) {
|
||||
args.comments = false;
|
||||
cssString = cssString.replace(commentX, '');
|
||||
}
|
||||
|
||||
while ((match = altX.exec(cssString)) != null) {
|
||||
if (!isEmpty(match[capComment]) && args.comments) {
|
||||
// Comment
|
||||
var add = match[capComment].trim();
|
||||
node[count++] = add;
|
||||
} else if (!isEmpty(match[capSelector])) {
|
||||
// New node, we recurse
|
||||
var name = match[capSelector].trim();
|
||||
// This will return when we encounter a closing brace
|
||||
var newNode = base.toJSON(cssString, args);
|
||||
if (args.ordered) {
|
||||
var obj = {};
|
||||
obj['name'] = name;
|
||||
obj['value'] = newNode;
|
||||
// Since we must use key as index to keep order and not
|
||||
// name, this will differentiate between a Rule Node and an
|
||||
// Attribute, since both contain a name and value pair.
|
||||
obj['type'] = 'rule';
|
||||
node[count++] = obj;
|
||||
} else {
|
||||
if (args.split) {
|
||||
var bits = name.split(',');
|
||||
} else {
|
||||
var bits = [name];
|
||||
}
|
||||
for (i in bits) {
|
||||
var sel = bits[i].trim();
|
||||
if (sel in node.children) {
|
||||
for (var att in newNode.attributes) {
|
||||
node.children[sel].attributes[att] = newNode.attributes[att];
|
||||
}
|
||||
} else {
|
||||
node.children[sel] = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!isEmpty(match[capEnd])) {
|
||||
// Node has finished
|
||||
return node;
|
||||
} else if (!isEmpty(match[capAttr])) {
|
||||
var line = match[capAttr].trim();
|
||||
var attr = lineAttrX.exec(line);
|
||||
if (attr) {
|
||||
// Attribute
|
||||
var name = attr[1].trim();
|
||||
var value = attr[2].trim();
|
||||
if (args.ordered) {
|
||||
var obj = {};
|
||||
obj['name'] = name;
|
||||
obj['value'] = value;
|
||||
obj['type'] = 'attr';
|
||||
node[count++] = obj;
|
||||
} else {
|
||||
if (name in node.attributes) {
|
||||
var currVal = node.attributes[name];
|
||||
if (!(currVal instanceof Array)) {
|
||||
node.attributes[name] = [currVal];
|
||||
}
|
||||
node.attributes[name].push(value);
|
||||
} else {
|
||||
node.attributes[name] = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Semicolon terminated line
|
||||
node[count++] = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param node
|
||||
* A JSON node.
|
||||
* @param depth
|
||||
* The depth of the current node; used for indentation and
|
||||
* optional.
|
||||
* @param breaks
|
||||
* Whether to add line breaks in the output.
|
||||
*/
|
||||
base.toCSS = function (node, depth, breaks) {
|
||||
var cssString = '';
|
||||
if (typeof depth == 'undefined') {
|
||||
depth = 0;
|
||||
}
|
||||
if (typeof breaks == 'undefined') {
|
||||
breaks = false;
|
||||
}
|
||||
if (node.attributes) {
|
||||
for (i in node.attributes) {
|
||||
var att = node.attributes[i];
|
||||
if (att instanceof Array) {
|
||||
for (var j = 0; j < att.length; j++) {
|
||||
cssString += strAttr(i, att[j], depth);
|
||||
}
|
||||
} else {
|
||||
cssString += strAttr(i, att, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.children) {
|
||||
var first = true;
|
||||
for (i in node.children) {
|
||||
if (breaks && !first) {
|
||||
cssString += '\n';
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
cssString += strNode(i, node.children[i], depth);
|
||||
}
|
||||
}
|
||||
return cssString;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param data
|
||||
* You can pass css string or the CSSJS.toJSON return value.
|
||||
* @param id (Optional)
|
||||
* To identify and easy removable of the style element
|
||||
* @param replace (Optional. defaults to TRUE)
|
||||
* Whether to remove or simply do nothing
|
||||
* @return HTMLLinkElement
|
||||
*/
|
||||
base.toHEAD = function (data, id, replace) {
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var xnode = document.getElementById(id);
|
||||
var _xnodeTest = (xnode !== null && xnode instanceof HTMLStyleElement);
|
||||
|
||||
if (isEmpty(data) || !(head instanceof HTMLHeadElement)) return;
|
||||
if (_xnodeTest) {
|
||||
if (replace === true || isEmpty(replace)) {
|
||||
xnode.removeAttribute('id');
|
||||
} else return;
|
||||
}
|
||||
if (isCssJson(data)) {
|
||||
data = base.toCSS(data);
|
||||
}
|
||||
|
||||
var node = document.createElement('style');
|
||||
node.type = 'text/css';
|
||||
|
||||
if (!isEmpty(id)) {
|
||||
node.id = id;
|
||||
} else {
|
||||
node.id = 'cssjson_' + timestamp();
|
||||
}
|
||||
if (node.styleSheet) {
|
||||
node.styleSheet.cssText = data;
|
||||
} else {
|
||||
node.appendChild(document.createTextNode(data));
|
||||
}
|
||||
|
||||
head.appendChild(node);
|
||||
|
||||
if (isValidStyleNode(node)) {
|
||||
if (_xnodeTest) {
|
||||
xnode.parentNode.removeChild(xnode);
|
||||
}
|
||||
} else {
|
||||
node.parentNode.removeChild(node);
|
||||
if (_xnodeTest) {
|
||||
xnode.setAttribute('id', id);
|
||||
node = xnode;
|
||||
} else return;
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
// Alias
|
||||
|
||||
if (typeof window != 'undefined') {
|
||||
window.createCSS = base.toHEAD;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
var isValidStyleNode = function (node) {
|
||||
return (node instanceof HTMLStyleElement) && node.sheet.cssRules.length > 0;
|
||||
}
|
||||
|
||||
var timestamp = function () {
|
||||
return Date.now() || +new Date();
|
||||
};
|
||||
|
||||
var strAttr = function (name, value, depth) {
|
||||
return '\t'.repeat(depth) + name + ': ' + value + ';\n';
|
||||
};
|
||||
|
||||
var strNode = function (name, value, depth) {
|
||||
var cssString = '\t'.repeat(depth) + name + ' {\n';
|
||||
cssString += base.toCSS(value, depth + 1);
|
||||
cssString += '\t'.repeat(depth) + '}\n';
|
||||
return cssString;
|
||||
};
|
||||
|
||||
};
|
||||
171
web-extension/libs/filesaver.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* FileSaver.js
|
||||
* A saveAs() FileSaver implementation.
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
*
|
||||
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
|
||||
* source : http://purl.eligrey.com/github/FileSaver.js
|
||||
*/
|
||||
|
||||
// The one and only way of getting global scope in all environments
|
||||
// https://stackoverflow.com/q/3277182/1008999
|
||||
var _global = typeof window === 'object' && window.window === window
|
||||
? window : typeof self === 'object' && self.self === self
|
||||
? self : typeof global === 'object' && global.global === global
|
||||
? global
|
||||
: this
|
||||
|
||||
function bom (blob, opts) {
|
||||
if (typeof opts === 'undefined') opts = { autoBom: false }
|
||||
else if (typeof opts !== 'object') {
|
||||
console.warn('Deprecated: Expected third argument to be a object')
|
||||
opts = { autoBom: !opts }
|
||||
}
|
||||
|
||||
// prepend BOM for UTF-8 XML and text/* types (including HTML)
|
||||
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
|
||||
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||
return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })
|
||||
}
|
||||
return blob
|
||||
}
|
||||
|
||||
function download (url, name, opts) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', url)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.onload = function () {
|
||||
saveAs(xhr.response, name, opts)
|
||||
}
|
||||
xhr.onerror = function () {
|
||||
console.error('could not download file')
|
||||
}
|
||||
xhr.send()
|
||||
}
|
||||
|
||||
function corsEnabled (url) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
// use sync to avoid popup blocker
|
||||
xhr.open('HEAD', url, false)
|
||||
try {
|
||||
xhr.send()
|
||||
} catch (e) {}
|
||||
return xhr.status >= 200 && xhr.status <= 299
|
||||
}
|
||||
|
||||
// `a.click()` doesn't work for all browsers (#465)
|
||||
function click (node) {
|
||||
try {
|
||||
node.dispatchEvent(new MouseEvent('click'))
|
||||
} catch (e) {
|
||||
var evt = document.createEvent('MouseEvents')
|
||||
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,
|
||||
20, false, false, false, false, 0, null)
|
||||
node.dispatchEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect WebView inside a native macOS app by ruling out all browsers
|
||||
// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
|
||||
// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
|
||||
var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)
|
||||
|
||||
var saveAs = _global.saveAs || (
|
||||
// probably in some web worker
|
||||
(typeof window !== 'object' || window !== _global)
|
||||
? function saveAs () { /* noop */ }
|
||||
|
||||
// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
|
||||
: ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)
|
||||
? function saveAs (blob, name, opts) {
|
||||
var URL = _global.URL || _global.webkitURL
|
||||
var a = document.createElement('a')
|
||||
name = name || blob.name || 'download'
|
||||
|
||||
a.download = name
|
||||
a.rel = 'noopener' // tabnabbing
|
||||
|
||||
// TODO: detect chrome extensions & packaged apps
|
||||
// a.target = '_blank'
|
||||
|
||||
if (typeof blob === 'string') {
|
||||
// Support regular links
|
||||
a.href = blob
|
||||
if (a.origin !== location.origin) {
|
||||
corsEnabled(a.href)
|
||||
? download(blob, name, opts)
|
||||
: click(a, a.target = '_blank')
|
||||
} else {
|
||||
click(a)
|
||||
}
|
||||
} else {
|
||||
// Support blobs
|
||||
a.href = URL.createObjectURL(blob)
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s
|
||||
setTimeout(function () { click(a) }, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Use msSaveOrOpenBlob as a second approach
|
||||
: 'msSaveOrOpenBlob' in navigator
|
||||
? function saveAs (blob, name, opts) {
|
||||
name = name || blob.name || 'download'
|
||||
|
||||
if (typeof blob === 'string') {
|
||||
if (corsEnabled(blob)) {
|
||||
download(blob, name, opts)
|
||||
} else {
|
||||
var a = document.createElement('a')
|
||||
a.href = blob
|
||||
a.target = '_blank'
|
||||
setTimeout(function () { click(a) })
|
||||
}
|
||||
} else {
|
||||
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to using FileReader and a popup
|
||||
: function saveAs (blob, name, opts, popup) {
|
||||
// Open a popup immediately do go around popup blocker
|
||||
// Mostly only available on user interaction and the fileReader is async so...
|
||||
popup = popup || open('', '_blank')
|
||||
if (popup) {
|
||||
popup.document.title =
|
||||
popup.document.body.innerText = 'downloading...'
|
||||
}
|
||||
|
||||
if (typeof blob === 'string') return download(blob, name, opts)
|
||||
|
||||
var force = blob.type === 'application/octet-stream'
|
||||
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari
|
||||
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent)
|
||||
|
||||
if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {
|
||||
// Safari doesn't allow downloading of blob URLs
|
||||
var reader = new FileReader()
|
||||
reader.onloadend = function () {
|
||||
var url = reader.result
|
||||
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
|
||||
if (popup) popup.location.href = url
|
||||
else location = url
|
||||
popup = null // reverse-tabnabbing #460
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
} else {
|
||||
var URL = _global.URL || _global.webkitURL
|
||||
var url = URL.createObjectURL(blob)
|
||||
if (popup) popup.location = url
|
||||
else location.href = url
|
||||
popup = null // reverse-tabnabbing #460
|
||||
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
_global.saveAs = saveAs.saveAs = saveAs
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = saveAs;
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Save As eBook",
|
||||
"version": "1.1",
|
||||
"name": "__MSG_extName__",
|
||||
"version": "1.4.2",
|
||||
"default_locale": "en",
|
||||
"author": "Alex Adam",
|
||||
"homepage_url": "https://github.com/alexadam/save-as-ebook",
|
||||
"description": "Save a web page or selection as eBook (.epub)",
|
||||
|
|
@ -10,7 +11,9 @@
|
|||
},
|
||||
"content_scripts": [{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["jquery.js", "jszip.js", "jszip-utils.js", "utils.js", "pure-parser.js", "extractHtml.js"]
|
||||
"js": ["./libs/jquery.js", "./libs/jszip.js", "./libs/jszip-utils.js",
|
||||
"./libs/pure-parser.js", "./libs/cssjson.js", "./libs/filesaver.js",
|
||||
"saveEbook.js", "extractHtml.js", "utils.js"]
|
||||
}],
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
|
|
@ -25,10 +28,33 @@
|
|||
"activeTab",
|
||||
"storage",
|
||||
"unlimitedStorage",
|
||||
"tabs",
|
||||
"notifications",
|
||||
"http://*/*",
|
||||
"https://*/*",
|
||||
"<all_urls>"
|
||||
]
|
||||
],
|
||||
"commands": {
|
||||
"save-page": {
|
||||
"suggested_key": {
|
||||
"default": "Alt+Shift+1"
|
||||
},
|
||||
"description": "Save current page as eBook"
|
||||
},
|
||||
"save-selection": {
|
||||
"suggested_key": {
|
||||
"default": "Alt+Shift+2"
|
||||
},
|
||||
"description": "Save current selection as eBook"
|
||||
},
|
||||
"add-page": {
|
||||
"suggested_key": {
|
||||
"default": "Alt+Shift+3"
|
||||
},
|
||||
"description": "Add current page as chapter"
|
||||
},
|
||||
"add-selection": {
|
||||
"suggested_key": {
|
||||
"default": "Alt+Shift+4"
|
||||
},
|
||||
"description": "Add current selection as chapter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,98 +2,162 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.menu-holder {
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
width: 200px;
|
||||
}
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#menuTitle {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.menu-holder {
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
width: 250px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
padding-left: 8px;
|
||||
border: none;
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
}
|
||||
#menuTitle {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
padding: 5px 0;
|
||||
font-family: sans-serif;
|
||||
padding-top: 10px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #007fff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
.optionContainer {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
#busy {
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.spinner {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
animation: rotate 0.8s infinite linear;
|
||||
border: 8px solid #fff;
|
||||
border-right-color: transparent;
|
||||
border-radius: 50%;
|
||||
left: 40%;
|
||||
top: 40%;
|
||||
position: absolute;
|
||||
}
|
||||
.wait-message {
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
}
|
||||
button {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
padding: 6px 0;
|
||||
padding-left: 8px;
|
||||
border: none;
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
button:hover {
|
||||
background-color: #007fff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#busy {
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
animation: rotate 0.8s infinite linear;
|
||||
border: 8px solid #fff;
|
||||
border-right-color: transparent;
|
||||
border-radius: 50%;
|
||||
left: 40%;
|
||||
top: 40%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.wait-message {
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#includeStyle {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
padding-bottom: 8px;
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
select {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
outline: none;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
#includeStyle {
|
||||
outline: none;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
.shortcut {
|
||||
font-size: 10px;
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="menu-holder">
|
||||
<div id="menuTitle">Save as eBook:</div>
|
||||
<button id="savePage" type="button" name="button">Save Page</button>
|
||||
<button id="saveSelection" type="button" name="button">Save Selection</button>
|
||||
<hr/>
|
||||
<button id="pageChapter" type="button" name="button">Add Page as Chapter</button>
|
||||
<button id="selectionChapter" type="button" name="button">Add Selection as Chapter</button>
|
||||
<hr/>
|
||||
<button id="editChapters" type="button" name="button">Edit Chapters ...</button>
|
||||
</div>
|
||||
<div id="busy">
|
||||
<div class="spinner"></div>
|
||||
<div class="wait-message">Please Wait...</div>
|
||||
<div class="menu-holder">
|
||||
<div id="menuTitle"></div>
|
||||
<hr/>
|
||||
<div class="optionContainer">
|
||||
<input type="checkbox" name="button" id="includeStyleCheck"></input><span id="includeStyle"></span>
|
||||
</div>
|
||||
<button id="editStyles" type="button" name="button"></button>
|
||||
<hr/>
|
||||
<button id="savePage" type="button" name="button">
|
||||
<div id="savePageLabel"></div>
|
||||
<div id="savePageShortcut" class="shortcut"></div>
|
||||
</button>
|
||||
<button id="saveSelection" type="button" name="button">
|
||||
<div id="saveSelectionLabel"></div>
|
||||
<div id="saveSelectionShortcut" class="shortcut"></div>
|
||||
</button>
|
||||
<hr/>
|
||||
<button id="pageChapter" type="button" name="button">
|
||||
<div id="pageChapterLabel"></div>
|
||||
<div id="pageChapterShortcut" class="shortcut"></div>
|
||||
</button>
|
||||
<button id="selectionChapter" type="button" name="button">
|
||||
<div id="selectionChapterLabel"></div>
|
||||
<div id="selectionChapterShortcut" class="shortcut"></div>
|
||||
</button>
|
||||
<hr/>
|
||||
<button id="editChapters" type="button" name="button"></button>
|
||||
</div>
|
||||
<div id="busy">
|
||||
<div class="spinner"></div>
|
||||
<div id="waitMessage" class="wait-message"></div>
|
||||
</div>
|
||||
|
||||
<script src="jquery.js" charset="utf-8"></script>
|
||||
<script src="filesaver.js" charset="utf-8"></script>
|
||||
<script src="jszip.js" charset="utf-8"></script>
|
||||
<script src="jszip-utils.js" charset="utf-8"></script>
|
||||
<script src="utils.js" charset="utf-8"></script>
|
||||
<script src="saveEbook.js" charset="utf-8"></script>
|
||||
<script src="menu.js" charset="utf-8"></script>
|
||||
<script src="menu.js" charset="utf-8"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,137 @@
|
|||
var allStyles = [];
|
||||
var currentStyle = null;
|
||||
var appliedStyles = [];
|
||||
|
||||
// create menu labels
|
||||
document.getElementById('menuTitle').innerHTML = chrome.i18n.getMessage('extName');
|
||||
document.getElementById('includeStyle').innerHTML = chrome.i18n.getMessage('includeStyle');
|
||||
document.getElementById('editStyles').innerHTML = chrome.i18n.getMessage('editStyles');
|
||||
document.getElementById('savePageLabel').innerHTML = chrome.i18n.getMessage('savePage');
|
||||
document.getElementById('saveSelectionLabel').innerHTML = chrome.i18n.getMessage('saveSelection');
|
||||
document.getElementById('pageChapterLabel').innerHTML = chrome.i18n.getMessage('pageChapter');
|
||||
document.getElementById('selectionChapterLabel').innerHTML = chrome.i18n.getMessage('selectionChapter');
|
||||
document.getElementById('editChapters').innerHTML = chrome.i18n.getMessage('editChapters');
|
||||
document.getElementById('waitMessage').innerHTML = chrome.i18n.getMessage('waitMessage');
|
||||
|
||||
function removeEbook() {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "remove"
|
||||
}, function(response) {});
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: "is busy?"
|
||||
}, function(response) {
|
||||
if (response.isBusy) {
|
||||
document.getElementById('busy').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('busy').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: "get styles"
|
||||
}, function(response) {
|
||||
createStyleList(response.styles);
|
||||
});
|
||||
|
||||
function createStyleList(styles) {
|
||||
allStyles = styles;
|
||||
chrome.tabs.query({'active': true}, function (tabs) {
|
||||
let currentUrl = tabs[0].url;
|
||||
|
||||
if (!styles || styles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let foundMatchingUrl = false;
|
||||
|
||||
// if multiple URL regexes match, select the longest one
|
||||
let allMatchingStyles = [];
|
||||
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
let listItem = document.createElement('option');
|
||||
listItem.id = 'option_' + i;
|
||||
listItem.className = 'cssEditor-chapter-item';
|
||||
listItem.value = 'option_' + i;
|
||||
listItem.innerText = styles[i].title;
|
||||
|
||||
currentUrl = currentUrl.replace(/(http[s]?:\/\/|www\.)/i, '').toLowerCase();
|
||||
let styleUrl = styles[i].url;
|
||||
let styleUrlRegex = null;
|
||||
|
||||
try {
|
||||
styleUrlRegex = new RegExp(styleUrl, 'i');
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if (styleUrlRegex && styleUrlRegex.test(currentUrl)) {
|
||||
allMatchingStyles.push({
|
||||
index: i,
|
||||
length: styleUrl.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (allMatchingStyles.length >= 1) {
|
||||
allMatchingStyles.sort(function (a, b) {
|
||||
return b.length - a.length;
|
||||
});
|
||||
let selStyle = allMatchingStyles[0];
|
||||
currentStyle = styles[selStyle.index];
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: "set current style",
|
||||
currentStyle: currentStyle
|
||||
}, function(response) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createIncludeStyle(data) {
|
||||
let includeStyleCheck = document.getElementById('includeStyleCheck');
|
||||
includeStyleCheck.checked = data;
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: "get include style"
|
||||
}, function(response) {
|
||||
createIncludeStyle(response.includeStyle);
|
||||
});
|
||||
|
||||
document.getElementById('includeStyleCheck').onclick = function () {
|
||||
let includeStyleCheck = document.getElementById('includeStyleCheck');
|
||||
chrome.runtime.sendMessage({
|
||||
type: "set include style",
|
||||
includeStyle: includeStyleCheck.checked
|
||||
}, function(response) {
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("editStyles").onclick = function() {
|
||||
|
||||
if (document.getElementById('cssEditor-Modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.tabs.query({
|
||||
currentWindow: true,
|
||||
active: true
|
||||
}, function(tab) {
|
||||
|
||||
chrome.tabs.insertCSS(tab[0].id, {file: '/cssEditor.css'});
|
||||
|
||||
chrome.tabs.executeScript(tab[0].id, {
|
||||
file: '/cssEditor.js'
|
||||
});
|
||||
|
||||
window.close();
|
||||
});
|
||||
};
|
||||
|
||||
document.getElementById("editChapters").onclick = function() {
|
||||
|
||||
|
||||
if (document.getElementById('chapterEditor-Modal')) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -10,13 +141,8 @@ document.getElementById("editChapters").onclick = function() {
|
|||
active: true
|
||||
}, function(tab) {
|
||||
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jquery.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/utils.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/filesaver.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jszip.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jszip-utils.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/saveEbook.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jquery-sortable.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: './libs/jquery.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: './libs/jquery-sortable.js'});
|
||||
chrome.tabs.insertCSS(tab[0].id, {file: '/chapterEditor.css'});
|
||||
|
||||
chrome.tabs.executeScript(tab[0].id, {
|
||||
|
|
@ -27,77 +153,46 @@ document.getElementById("editChapters").onclick = function() {
|
|||
});
|
||||
};
|
||||
|
||||
function dispatch(action, justAddToBuffer) {
|
||||
function dispatch(commandType, justAddToBuffer) {
|
||||
document.getElementById('busy').style.display = 'block';
|
||||
if (!justAddToBuffer) {
|
||||
removeEbook();
|
||||
}
|
||||
chrome.tabs.query({
|
||||
currentWindow: true,
|
||||
active: true
|
||||
}, function(tab) {
|
||||
chrome.tabs.sendMessage(tab[0].id, {
|
||||
type: 'echo'
|
||||
}, function(response) {
|
||||
if (!response) {
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jquery.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/utils.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/filesaver.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jszip.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/jszip-utils.js'});
|
||||
chrome.tabs.executeScript(tab[0].id, {file: '/pure-parser.js'});
|
||||
|
||||
chrome.tabs.executeScript(tab[0].id, {
|
||||
file: 'extractHtml.js'
|
||||
}, function() {
|
||||
sendMessage(tab[0].id, action, justAddToBuffer);
|
||||
});
|
||||
} else if (response.echo) {
|
||||
sendMessage(tab[0].id, action, justAddToBuffer);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendMessage(tabId, action, justAddToBuffer) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
type: action
|
||||
chrome.runtime.sendMessage({
|
||||
type: commandType
|
||||
}, function(response) {
|
||||
if (response.length === 0) {
|
||||
if (justAddToBuffer) {
|
||||
alert('Cannot add an empty selection as chapter!');
|
||||
} else {
|
||||
alert('Cannot generate the eBook from an empty selection!');
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
if (!justAddToBuffer) {
|
||||
buildEbook([response]);
|
||||
} else {
|
||||
getEbookPages(function (allPages) {
|
||||
allPages.push(response);
|
||||
saveEbookPages(allPages);
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
setTimeout(function () {
|
||||
document.getElementById('busy').style.display = 'none';
|
||||
}, 500);
|
||||
//FIXME - hidden before done
|
||||
document.getElementById('busy').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('savePage').onclick = function() {
|
||||
dispatch('extract-page', false);
|
||||
dispatch('save-page', false);
|
||||
};
|
||||
|
||||
document.getElementById('saveSelection').onclick = function() {
|
||||
dispatch('extract-selection', false);
|
||||
dispatch('save-selection', false);
|
||||
};
|
||||
|
||||
document.getElementById('pageChapter').onclick = function() {
|
||||
dispatch('extract-page', true);
|
||||
dispatch('add-page', true);
|
||||
};
|
||||
|
||||
document.getElementById('selectionChapter').onclick = function() {
|
||||
dispatch('extract-selection', true);
|
||||
dispatch('add-selection', true);
|
||||
};
|
||||
|
||||
// get all shortcuts and display them in the menuTitle
|
||||
chrome.commands.getAll((commands) => {
|
||||
for (let command of commands) {
|
||||
if (command.name === 'save-page') {
|
||||
document.getElementById('savePageShortcut').appendChild(document.createTextNode(command.shortcut));
|
||||
} else if (command.name === 'save-selection') {
|
||||
document.getElementById('saveSelectionShortcut').appendChild(document.createTextNode(command.shortcut));
|
||||
} else if (command.name === 'add-page') {
|
||||
document.getElementById('pageChapterShortcut').appendChild(document.createTextNode(command.shortcut));
|
||||
} else if (command.name === 'add-selection') {
|
||||
document.getElementById('selectionChapterShortcut').appendChild(document.createTextNode(command.shortcut));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
var cssFileName = 'ebook.css';
|
||||
var ebookTitle = null;
|
||||
|
||||
chrome.runtime.onMessage.addListener((obj, sender, sendResponse) => {
|
||||
if (obj.shortcut && obj.shortcut === 'build-ebook') {
|
||||
buildEbook(obj.response);
|
||||
} else if (obj.alert) {
|
||||
console.log(obj.alert);
|
||||
alert(obj.alert);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
|
||||
function getImagesIndex(allImages) {
|
||||
return allImages.reduce(function(prev, elem, index) {
|
||||
return prev + '\n' + '<item href="images/' + elem.filename + '" id="img' + elem.filename + '" media-type="image/' + getImageType(elem.filename) + '"/>';
|
||||
|
|
@ -23,22 +33,28 @@ function buildEbookFromChapters() {
|
|||
})
|
||||
}
|
||||
|
||||
function buildEbook(allPages) {
|
||||
_buildEbook(allPages);
|
||||
// FIXME remove - keep one function
|
||||
function buildEbook(allPages, fromMenu=false) {
|
||||
_buildEbook(allPages, fromMenu);
|
||||
}
|
||||
|
||||
// http://ebooks.stackexchange.com/questions/1183/what-is-the-minimum-required-content-for-a-valid-epub
|
||||
function _buildEbook(allPages) {
|
||||
function _buildEbook(allPages, fromMenu=false) {
|
||||
allPages = allPages.filter(function(page) {
|
||||
return page !== null;
|
||||
});
|
||||
|
||||
console.log('Prepare Content...');
|
||||
|
||||
var ebookFileName = 'eBook.epub';
|
||||
|
||||
if (ebookTitle) {
|
||||
ebookName = ebookTitle + '.epub';
|
||||
// ~TODO a pre-processing function to apply escapeXMLChars to all page.titles
|
||||
ebookName = escapeXMLChars(ebookTitle);
|
||||
ebookFileName = getEbookFileName(removeSpecialChars(ebookTitle)) + '.epub';
|
||||
} else {
|
||||
ebookName = allPages[0].title + '.epub';
|
||||
ebookName = escapeXMLChars(allPages[0].title);
|
||||
ebookFileName = getEbookFileName(removeSpecialChars(allPages[0].title)) + '.epub';
|
||||
}
|
||||
|
||||
var zip = new JSZip();
|
||||
|
|
@ -68,7 +84,8 @@ function _buildEbook(allPages) {
|
|||
'<h1 class="frontmatter">Table of Contents</h1>' +
|
||||
'<ol class="contents">' +
|
||||
allPages.reduce(function(prev, page) {
|
||||
return prev + '\n' + '<li><a href="pages/' + page.url + '">' + page.title + '</a></li>';
|
||||
var tmpPageTitle = escapeXMLChars(page.title);
|
||||
return prev + '\n' + '<li><a href="pages/' + page.url + '">' + tmpPageTitle + '</a></li>';
|
||||
}, '') +
|
||||
'</ol>' +
|
||||
'</nav>' +
|
||||
|
|
@ -88,9 +105,10 @@ function _buildEbook(allPages) {
|
|||
'</docTitle>' +
|
||||
'<navMap>' +
|
||||
allPages.reduce(function(prev, page, index) {
|
||||
var tmpPageTitle = escapeXMLChars(page.title);
|
||||
return prev + '\n' +
|
||||
'<navPoint id="ebook' + index + '" playOrder="' + (index + 1) + '">' +
|
||||
'<navLabel><text>' + page.title + '</text></navLabel>' +
|
||||
'<navLabel><text>' + tmpPageTitle + '</text></navLabel>' +
|
||||
'<content src="pages/' + page.url + '" />' +
|
||||
'</navPoint>';
|
||||
}, '') +
|
||||
|
|
@ -98,16 +116,21 @@ function _buildEbook(allPages) {
|
|||
'</ncx>'
|
||||
);
|
||||
|
||||
oebps.file(cssFileName, '');
|
||||
oebps.file(cssFileName, ''); //TODO
|
||||
var styleFolder = oebps.folder('style');
|
||||
allPages.forEach(function(page) {
|
||||
styleFolder.file(page.styleFileName, page.styleFileContent);
|
||||
});
|
||||
|
||||
var pagesFolder = oebps.folder('pages');
|
||||
allPages.forEach(function(page) {
|
||||
var tmpPageTitle = escapeXMLChars(page.title);
|
||||
pagesFolder.file(page.url,
|
||||
'<?xml version="1.0" encoding="utf-8"?>' +
|
||||
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">' +
|
||||
'<head>' +
|
||||
'<title>' + page.title + '</title>' +
|
||||
'<link href="../' + cssFileName + '" rel="stylesheet" type="text/css" />' +
|
||||
'<title>' + tmpPageTitle+ '</title>' +
|
||||
'<link href="../style/' + page.styleFileName + '" rel="stylesheet" type="text/css" />' +
|
||||
'</head><body>' +
|
||||
page.content +
|
||||
'</body></html>'
|
||||
|
|
@ -120,7 +143,7 @@ function _buildEbook(allPages) {
|
|||
'<metadata>' +
|
||||
'<dc:title id="t1">'+ ebookName + '</dc:title>' +
|
||||
'<dc:identifier id="db-id">isbn</dc:identifier>' +
|
||||
'<meta property="dcterms:modified">' + new Date().toISOString() + '</meta>' +
|
||||
'<meta property="dcterms:modified">' + new Date().toISOString().replace(/\.[0-9]+Z/i, 'Z') + '</meta>' +
|
||||
'<dc:language>en</dc:language>' +
|
||||
'</metadata>' +
|
||||
'<manifest>' +
|
||||
|
|
@ -130,10 +153,12 @@ function _buildEbook(allPages) {
|
|||
allPages.reduce(function(prev, page, index) {
|
||||
return prev + '\n' + '<item id="ebook' + index + '" href="pages/' + page.url + '" media-type="application/xhtml+xml" />';
|
||||
}, '') +
|
||||
allPages.reduce(function(prev, page, index) {
|
||||
return prev + '\n' + '<item id="style' + index + '" href="style/' + page.styleFileName + '" media-type="text/css" />';
|
||||
}, '') +
|
||||
allPages.reduce(function(prev, page, index) {
|
||||
return prev + '\n' + getImagesIndex(page.images);
|
||||
}, '') +
|
||||
// getExternalLinksIndex() +
|
||||
'</manifest>' +
|
||||
'<spine toc="ncx">' +
|
||||
allPages.reduce(function(prev, page, index) {
|
||||
|
|
@ -144,37 +169,35 @@ function _buildEbook(allPages) {
|
|||
);
|
||||
|
||||
///////////////
|
||||
var imgsFolder = oebps.folder("images");
|
||||
allPages.forEach(function(page) {
|
||||
for (var i = 0; i < page.images.length; i++) {
|
||||
var tmpImg = page.images[i];
|
||||
imgsFolder.file(tmpImg.filename, tmpImg.data, {
|
||||
base64: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var done = false;
|
||||
try {
|
||||
let imgsFolder = oebps.folder("images");
|
||||
allPages.forEach(function(page) {
|
||||
for (let i = 0; i < page.images.length; i++) {
|
||||
let tmpImg = page.images[i]
|
||||
// TODO - Must be JSON serializable - see the same comment in extractHtml.js
|
||||
// if (tmpImg.isBinary) {
|
||||
// imgsFolder.file(tmpImg.filename, tmpImg.data, {binary: true})
|
||||
// } else {
|
||||
imgsFolder.file(tmpImg.filename, tmpImg.data, {base64: true})
|
||||
// }
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
|
||||
zip.generateAsync({
|
||||
type: "blob"
|
||||
type: "blob",
|
||||
mimeType: "application/epub+zip"
|
||||
})
|
||||
.then(function(content) {
|
||||
done = true;
|
||||
console.log("done !");
|
||||
saveAs(content, ebookName);
|
||||
saveAs(content, ebookFileName);
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: "done"
|
||||
}, (response) => {});
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
zip.generateAsync({
|
||||
type: "blob"
|
||||
})
|
||||
.then(function(content) {
|
||||
saveAs(content, ebookName);
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
}
|
||||
|
|
|
|||
140
web-extension/selector.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
var lastTargetElem = null
|
||||
var lastElementData = {}
|
||||
var lastParentElem = null
|
||||
var lastParentData = {}
|
||||
|
||||
let cleanXPaths = []
|
||||
|
||||
|
||||
let bodyElem = document.getElementsByTagName('body')[0]
|
||||
let bodyInner = bodyElem.innerHTML
|
||||
bodyElem.innerHTML = ''
|
||||
|
||||
// let container = document.createElement('div')
|
||||
// container.style.display = "flex"
|
||||
// container.style.flexFlow = "row"
|
||||
// let menuContainer = document.createElement('div')
|
||||
// menuContainer.style.width = '300px'
|
||||
// menuContainer.style.minWidth = '300px'
|
||||
// menuContainer.style.height = '600px'
|
||||
// menuContainer.style.minHeight = '600px'
|
||||
// let page = document.createElement('div')
|
||||
// page.id= 'super-selector'
|
||||
// page.style.width = '90%'
|
||||
// page.style.height = '90%'
|
||||
// page.innerHTML = bodyInner
|
||||
|
||||
// let menu = document.createElement('div')
|
||||
// menu.id = "slector-main-menu"
|
||||
// menu.style.width = '300px'
|
||||
// menu.style.minWidth = '300px'
|
||||
// menu.style.height = '600px'
|
||||
// menu.style.minHeight = '600px'
|
||||
// menu.style.backgroundColor = 'black'
|
||||
// menu.style.position = 'fixed'
|
||||
// menu.style.top = '0'
|
||||
|
||||
|
||||
// container.appendChild(menuContainer)
|
||||
// container.appendChild(page)
|
||||
// bodyElem.appendChild(container)
|
||||
// bodyElem.appendChild(menu)
|
||||
|
||||
bodyElem.innerHTML = `<div style="width: 100%;">
|
||||
<div id="super-selector" style="max-width:75%; position: absolute;">${bodyInner}'</div>
|
||||
<div id="slector-main-menu" style="width:25%; min-width: 300px; height: 100%; right: 0; position: fixed;">MENU</div>
|
||||
</div>`
|
||||
|
||||
|
||||
document.getElementById('super-selector').addEventListener('mousemove', (e) => {
|
||||
// document.getElementsByTagName('body')[0].addEventListener('mousemove', (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (lastTargetElem) {
|
||||
lastTargetElem.style.backgroundColor = lastElementData.backgroundColor
|
||||
}
|
||||
|
||||
let targetElem = document.elementFromPoint(e.clientX, e.clientY);
|
||||
|
||||
let targetParent = targetElem.parentNode
|
||||
lastElementData.backgroundColor = targetElem.style.backgroundColor
|
||||
|
||||
targetElem.style.backgroundColor = 'rgba(255,0,0,0.25)'
|
||||
targetElem.dataset.excluded = 'true'
|
||||
|
||||
targetElem.onclick = (e) => {
|
||||
e.preventDefault() // FIXME except for my menu
|
||||
e.stopPropagation();
|
||||
|
||||
let originalClickedElem = e.target
|
||||
let clickedElem = e.target
|
||||
|
||||
if (!clickedElem) {
|
||||
return
|
||||
}
|
||||
|
||||
// console.log(createXPathFromElement(clickedElem));
|
||||
|
||||
///
|
||||
let tmpElem = document.createElement('div')
|
||||
tmpElem.innerText = createXPathFromElement(clickedElem);
|
||||
document.getElementById('slector-main-menu').appendChild(tmpElem)
|
||||
|
||||
let clickedElemParent = clickedElem.parentNode;
|
||||
let foundClickedEleme = false
|
||||
while (clickedElemParent) {
|
||||
if (!clickedElemParent || !clickedElemParent.style) {
|
||||
break
|
||||
}
|
||||
if (clickedElemParent.id === 'slector-main-menu') {
|
||||
// ignore main menu
|
||||
return
|
||||
}
|
||||
if (clickedElemParent.style.opacity === '0.1') {
|
||||
foundClickedEleme = true
|
||||
clickedElem = clickedElemParent
|
||||
break
|
||||
}
|
||||
clickedElemParent = clickedElemParent.parentNode;
|
||||
}
|
||||
|
||||
if (!clickedElem.style.opacity || clickedElem.style.opacity > 0.2) {
|
||||
clickedElem.style.opacity = 0.1
|
||||
clickedElem.style.border = 'solid 3px black'
|
||||
}
|
||||
else {
|
||||
clickedElem.style.opacity = 1
|
||||
clickedElem.style.border = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
lastTargetElem = targetElem
|
||||
})
|
||||
|
||||
// src https://stackoverflow.com/questions/2661818/javascript-get-xpath-of-a-node
|
||||
function createXPathFromElement(elm) {
|
||||
var allNodes = document.getElementsByTagName('*');
|
||||
for (var segs = []; elm && elm.nodeType == 1; elm = elm.parentNode)
|
||||
{
|
||||
if (elm.hasAttribute('id')) {
|
||||
var uniqueIdCount = 0;
|
||||
for (var n=0;n < allNodes.length;n++) {
|
||||
if (allNodes[n].hasAttribute('id') && allNodes[n].id == elm.id) uniqueIdCount++;
|
||||
if (uniqueIdCount > 1) break;
|
||||
};
|
||||
if ( uniqueIdCount == 1) {
|
||||
segs.unshift('id("' + elm.getAttribute('id') + '")');
|
||||
return segs.join('/');
|
||||
} else {
|
||||
segs.unshift(elm.localName.toLowerCase() + '[@id="' + elm.getAttribute('id') + '"]');
|
||||
}
|
||||
} else if (elm.hasAttribute('class')) {
|
||||
segs.unshift(elm.localName.toLowerCase() + '[@class="' + elm.getAttribute('class') + '"]');
|
||||
} else {
|
||||
for (i = 1, sib = elm.previousSibling; sib; sib = sib.previousSibling) {
|
||||
if (sib.localName == elm.localName) i++; };
|
||||
segs.unshift(elm.localName.toLowerCase() + '[' + i + ']');
|
||||
};
|
||||
};
|
||||
return segs.length ? '/' + segs.join('/') : null;
|
||||
};
|
||||
|
|
@ -1,3 +1,51 @@
|
|||
function setIncludeStyle(includeStyle) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "set include style",
|
||||
includeStyle: includeStyle
|
||||
}, function(response) {
|
||||
});
|
||||
}
|
||||
|
||||
function getIncludeStyle(callback) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "get include style"
|
||||
}, function(response) {
|
||||
callback(response.includeStyle);
|
||||
});
|
||||
}
|
||||
|
||||
function setCurrentStyle(currentStyle) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "set current style",
|
||||
currentStyle: currentStyle
|
||||
}, function(response) {
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentStyle(callback) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "get current style"
|
||||
}, function(response) {
|
||||
callback(response.currentStyle);
|
||||
});
|
||||
}
|
||||
|
||||
function getStyles(callback) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "get styles"
|
||||
}, function(response) {
|
||||
callback(response.styles);
|
||||
});
|
||||
}
|
||||
|
||||
function setStyles(styles) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "set styles",
|
||||
styles: styles
|
||||
}, function(response) {
|
||||
});
|
||||
}
|
||||
|
||||
function getEbookTitle(callback) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "get title"
|
||||
|
|
@ -35,9 +83,24 @@ function removeEbook() {
|
|||
}, function(response) {});
|
||||
}
|
||||
|
||||
function checkIfBusy(callback) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "is busy?"
|
||||
}, function(response) {
|
||||
callback(response);
|
||||
});
|
||||
}
|
||||
|
||||
function setIsBusy(isBusy) {
|
||||
chrome.runtime.sendMessage({
|
||||
type: "set is busy",
|
||||
isBusy: isBusy
|
||||
}, function(response) {});
|
||||
}
|
||||
|
||||
/////
|
||||
function getCurrentUrl() {
|
||||
var url = window.location.href;
|
||||
let url = window.location.href;
|
||||
if (url.indexOf('?') > 0) {
|
||||
url = window.location.href.split('?')[0];
|
||||
}
|
||||
|
|
@ -46,7 +109,7 @@ function getCurrentUrl() {
|
|||
}
|
||||
|
||||
function getOriginUrl() {
|
||||
var originUrl = window.location.origin;
|
||||
let originUrl = window.location.origin;
|
||||
if (!originUrl) {
|
||||
originUrl = window.location.protocol + "//" + window.location.host;
|
||||
}
|
||||
|
|
@ -55,7 +118,7 @@ function getOriginUrl() {
|
|||
|
||||
function getFileExtension(fileName) {
|
||||
try {
|
||||
var tmpFileName = '';
|
||||
let tmpFileName = '';
|
||||
|
||||
if (isBase64Img(fileName)) {
|
||||
tmpFileName = getBase64ImgType(fileName);
|
||||
|
|
@ -71,8 +134,10 @@ function getFileExtension(fileName) {
|
|||
tmpFileName = 'jpeg';
|
||||
} else if (tmpFileName === 'svg+xml') {
|
||||
tmpFileName = 'svg';
|
||||
} else if (tmpFileName.trim() === '') {
|
||||
tmpFileName = '';
|
||||
}
|
||||
|
||||
if (['png', 'gif', 'jpeg', 'svg'].indexOf(tmpFileName.trim()) < 0) {
|
||||
return ''
|
||||
}
|
||||
return tmpFileName;
|
||||
} catch (e) {
|
||||
|
|
@ -82,7 +147,7 @@ function getFileExtension(fileName) {
|
|||
}
|
||||
|
||||
function getImageType(fileName) {
|
||||
var imageType = getFileExtension(fileName);
|
||||
let imageType = getFileExtension(fileName);
|
||||
if (imageType === 'svg') {
|
||||
imageType = 'svg+xml';
|
||||
}
|
||||
|
|
@ -97,18 +162,21 @@ function getImgDownloadUrl(imgSrc) {
|
|||
return getAbsoluteUrl(imgSrc);
|
||||
}
|
||||
|
||||
function getAbsoluteUrl(urlStr) {
|
||||
function getAbsoluteUrl(urlStr) {
|
||||
if (!urlStr) {
|
||||
return '';
|
||||
}
|
||||
if (urlStr.length === 0) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
if (urlStr.length === 0) {
|
||||
return '';
|
||||
}
|
||||
urlStr = decodeHtmlEntity(urlStr);
|
||||
var currentUrl = getCurrentUrl();
|
||||
var originUrl = getOriginUrl();
|
||||
var absoluteUrl = urlStr;
|
||||
urlStr = decodeHtmlEntity(urlStr);
|
||||
let currentUrl = getCurrentUrl();
|
||||
let originUrl = getOriginUrl();
|
||||
let absoluteUrl = urlStr;
|
||||
|
||||
originUrl = removeEndingSlash(originUrl)
|
||||
currentUrl = removeEndingSlash(currentUrl)
|
||||
|
||||
if (urlStr.indexOf('//') === 0) {
|
||||
absoluteUrl = window.location.protocol + urlStr;
|
||||
|
|
@ -119,6 +187,8 @@ function getAbsoluteUrl(urlStr) {
|
|||
} else if (urlStr.indexOf('http') !== 0) {
|
||||
absoluteUrl = currentUrl + '/' + urlStr;
|
||||
}
|
||||
// TODO is this needed?
|
||||
// absoluteUrl = escapeXMLChars(absoluteUrl);
|
||||
return absoluteUrl;
|
||||
} catch (e) {
|
||||
console.log('Error:', e);
|
||||
|
|
@ -126,20 +196,27 @@ function getAbsoluteUrl(urlStr) {
|
|||
}
|
||||
}
|
||||
|
||||
function removeEndingSlash(inputStr) {
|
||||
if (inputStr.endsWith('/')) {
|
||||
return inputStr.substring(0, inputStr.length - 1);
|
||||
}
|
||||
return inputStr;
|
||||
}
|
||||
|
||||
// https://gist.github.com/jonleighton/958841
|
||||
function base64ArrayBuffer(arrayBuffer) {
|
||||
var base64 = '';
|
||||
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
let base64 = '';
|
||||
let encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
var bytes = new Uint8Array(arrayBuffer);
|
||||
var byteLength = bytes.byteLength;
|
||||
var byteRemainder = byteLength % 3;
|
||||
var mainLength = byteLength - byteRemainder;
|
||||
let bytes = new Uint8Array(arrayBuffer);
|
||||
let byteLength = bytes.byteLength;
|
||||
let byteRemainder = byteLength % 3;
|
||||
let mainLength = byteLength - byteRemainder;
|
||||
|
||||
var a, b, c, d;
|
||||
var chunk;
|
||||
let a, b, c, d;
|
||||
let chunk;
|
||||
|
||||
for (var i = 0; i < mainLength; i = i + 3) {
|
||||
for (let i = 0; i < mainLength; i = i + 3) {
|
||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
||||
a = (chunk & 16515072) >> 18;
|
||||
b = (chunk & 258048) >> 12;
|
||||
|
|
@ -194,11 +271,13 @@ function getBase64ImgData(srcTxt) {
|
|||
}
|
||||
|
||||
function getXPath(elm) {
|
||||
var allNodes = document.getElementsByTagName('*');
|
||||
for (var segs = []; elm && elm.nodeType === 1; elm = elm.parentNode) {
|
||||
if (!elm) return ''
|
||||
|
||||
let allNodes = document.getElementsByTagName('*');
|
||||
for (let segs = []; elm && elm.nodeType === 1; elm = elm.parentNode) {
|
||||
if (elm.hasAttribute('id')) {
|
||||
var uniqueIdCount = 0;
|
||||
for (var n = 0; n < allNodes.length; n++) {
|
||||
let uniqueIdCount = 0;
|
||||
for (let n = 0; n < allNodes.length; n++) {
|
||||
if (allNodes[n].hasAttribute('id') && allNodes[n].id === elm.id) {
|
||||
uniqueIdCount++;
|
||||
}
|
||||
|
|
@ -227,17 +306,71 @@ function getXPath(elm) {
|
|||
}
|
||||
|
||||
function lookupElementByXPath(path) {
|
||||
var evaluator = new XPathEvaluator();
|
||||
var result = evaluator.evaluate(path, document.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
||||
let evaluator = new XPathEvaluator();
|
||||
let result = evaluator.evaluate(path, document.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
||||
return result.singleNodeValue;
|
||||
}
|
||||
|
||||
function generateRandomTag(tagLen) {
|
||||
tagLen = tagLen || 5;
|
||||
var text = '';
|
||||
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
for(var i = 0; i < tagLen; i++) {
|
||||
let text = '';
|
||||
let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
for(let i = 0; i < tagLen; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function generateRandomNumber(bigNr) {
|
||||
if (bigNr) {
|
||||
return Math.floor(Math.random()*1000000*Math.random()*1000000)
|
||||
}
|
||||
return Math.floor(Math.random()*1000000)
|
||||
}
|
||||
|
||||
function removeSpecialChars(text) {
|
||||
// FIXME remove white spaces ?
|
||||
return text.replace(/\//g, '-')
|
||||
}
|
||||
|
||||
function escapeXMLChars(text) {
|
||||
return text.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function getEbookFileName(name) {
|
||||
return name.replace(/&/ig, '&')
|
||||
.replace(/>/ig, '')
|
||||
.replace(/</ig, '')
|
||||
.replace(/"/ig, '')
|
||||
.replace(/'/ig, '');
|
||||
}
|
||||
|
||||
function getPageUrl(url) {
|
||||
return url.toLowerCase().replace(/\s+/g,'_').replace(/[^a-z0-9_]/g,'') + Math.floor(Math.random() * 10000) + '.xhtml';
|
||||
}
|
||||
|
||||
function getPageTitle(title) {
|
||||
if (title.trim().length === 0) {
|
||||
return 'ebook';
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
function jsonToCss(jsonObj) {
|
||||
let keys = Object.keys(jsonObj);
|
||||
let result = '';
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let tmpJsonObj = jsonObj[keys[i]];
|
||||
let tmpKeys = Object.keys(tmpJsonObj);
|
||||
result += '.' + keys[i] + ' {';
|
||||
for (let j = 0; j < tmpKeys.length; j++) {
|
||||
result += tmpKeys[j] + ':' + tmpJsonObj[tmpKeys[j]] + ';';
|
||||
}
|
||||
result += '} ';
|
||||
}
|
||||
return result;
|
||||
}
|
||||