diff --git a/application/lib/build_ebook.py b/application/lib/build_ebook.py index e13720df..dd9a7829 100644 --- a/application/lib/build_ebook.py +++ b/application/lib/build_ebook.py @@ -9,24 +9,28 @@ from calibre.web.feeds.recipes import compile_recipe from recipe_helper import GenerateRecipeSource from urlopener import UrlOpener +from application.utils import loc_exc_pos #从输入格式生成对应的输出格式 -#recipes: 编译后的recipe,为一个列表 +#input_: 如果是recipe,为编译后的recipe(或列表),或者是一个输入文件名,或一个BytesIO +#input_fmt: 输入格式, recipe, mobi, ... #user: KeUser对象 #output_fmt: 如果指定,则生成特定格式的书籍,否则使用user.book_cfg('type') #options: 额外的一些参数,为一个字典 # 如: options={'debug_pipeline': path, 'verbose': 1} #返回电子书二进制内容 -def recipes_to_ebook(recipes: list, user, options=None, output_fmt=''): - if not isinstance(recipes, list): - recipes = [recipes] +def convert_book(input_, input_fmt, user, options=None, output_fmt=''): output = io.BytesIO() - output_fmt=output_fmt if output_fmt else user.book_cfg('type') + output_fmt = output_fmt if output_fmt else user.book_cfg('type') options = ke_opts(user, options) - plumber = Plumber(recipes, output, input_fmt='recipe', output_fmt=output_fmt, options=options) - plumber.run() - return output.getvalue() - + plumber = Plumber(input_, output, input_fmt=input_fmt, output_fmt=output_fmt, options=options) + try: + plumber.run() + return output.getvalue() + except: + default_log.warning(loc_exc_pos('convert_book failed')) + return b'' + #仅通过一个url列表构建一本电子书 #urls: [(title, url),...] or [url,url,...] #title: 书籍标题 @@ -84,7 +88,7 @@ def clearPrevDownloads(): #退出时清理临时文件 userCss = user.get_extra_css() ro.extra_css = f'{ro.extra_css}\n\n{userCss}' if ro.extra_css else userCss #type:ignore - book = recipes_to_ebook([ro], user, options, output_fmt) + book = convert_book(ro, 'recipe', user, options, output_fmt) clearPrevDownloads() return book diff --git a/application/lib/calibre/customize/builtins.py b/application/lib/calibre/customize/builtins.py index 8aa6678f..27ebb34f 100644 --- a/application/lib/calibre/customize/builtins.py +++ b/application/lib/calibre/customize/builtins.py @@ -36,10 +36,11 @@ def set_metadata(self, stream, mi, type): from calibre.ebooks.conversion.plugins.html_input import HTMLInput from calibre.ebooks.conversion.plugins.epub_input import EPUBInput from calibre.ebooks.conversion.plugins.epub_output import EPUBOutput +from calibre.ebooks.conversion.plugins.mobi_input import MOBIInput from calibre.ebooks.conversion.plugins.mobi_output import (MOBIOutput, AZW3Output) from calibre.ebooks.conversion.plugins.oeb_output import OEBOutput -plugins = [RecipeInput, HTMLInput, EPUBOutput, MOBIOutput, AZW3Output, OEBOutput, EPUBMetadataWriter, MOBIMetadataWriter] +plugins = [RecipeInput, HTMLInput, MOBIInput, EPUBOutput, MOBIOutput, AZW3Output, OEBOutput, EPUBMetadataWriter, MOBIMetadataWriter] from calibre.customize.profiles import input_profiles, output_profiles plugins += input_profiles diff --git a/application/lib/calibre/customize/conversion.py b/application/lib/calibre/customize/conversion.py index 4bf000f7..63c7667e 100644 --- a/application/lib/calibre/customize/conversion.py +++ b/application/lib/calibre/customize/conversion.py @@ -197,7 +197,7 @@ def get_images(self): raise NotImplementedError() #fs: FsDictStub 对象,由它根据情况使用内存缓存或使用磁盘缓存 - def convert(self, stream, options, file_ext, log, output_dir, fs): + def convert(self, stream, opts, file_ext, log, output_dir, fs): ''' This method must be implemented in sub-classes. It must return the path to the created OPF file or an :class:`OEBBook` instance. diff --git a/application/lib/calibre/ebooks/compression/mobi_uncompress.py b/application/lib/calibre/ebooks/compression/mobi_uncompress.py new file mode 100644 index 00000000..2235bcce --- /dev/null +++ b/application/lib/calibre/ebooks/compression/mobi_uncompress.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +import struct + +class unpackException(Exception): + pass + +class UncompressedReader: + def unpack(self, data): + return data + +class PalmdocReader: + def unpack(self, i): + o, p = b"", 0 + while p < len(i): + # for python 3 must use slice since i[p] returns int while slice returns character + c = ord(i[p : p + 1]) + p += 1 + if c >= 1 and c <= 8: + o += i[p : p + c] + p += c + elif c < 128: + o += bytes([c]) + elif c >= 192: + o += b" " + bytes([c ^ 128]) + else: + if p < len(i): + c = (c << 8) | ord(i[p : p + 1]) + p += 1 + m = (c >> 3) & 0x07FF + n = (c & 7) + 3 + if m > n: + o += o[-m : n - m] + else: + for _ in range(n): + # because of completely ass-backwards decision by python mainters for python 3 + # we must use slice for bytes as i[p] returns int while slice returns character + if m == 1: + o += o[-m:] + else: + o += o[-m : -m + 1] + return o + + +class HuffcdicReader: + q = struct.Struct(b">Q").unpack_from + + def loadHuff(self, huff): + if huff[0:8] != b"HUFF\x00\x00\x00\x18": + raise unpackException("invalid huff header") + off1, off2 = struct.unpack_from(b">LL", huff, 8) + + def dict1_unpack(v): + codelen, term, maxcode = v & 0x1F, v & 0x80, v >> 8 + assert codelen != 0 + if codelen <= 8: + assert term + maxcode = ((maxcode + 1) << (32 - codelen)) - 1 + return (codelen, term, maxcode) + + self.dict1 = list(map(dict1_unpack, struct.unpack_from(b">256L", huff, off1))) + + dict2 = struct.unpack_from(b">64L", huff, off2) + self.mincode, self.maxcode = (), () + for codelen, mincode in enumerate((0,) + dict2[0::2]): + self.mincode += (mincode << (32 - codelen),) + for codelen, maxcode in enumerate((0,) + dict2[1::2]): + self.maxcode += (((maxcode + 1) << (32 - codelen)) - 1,) + + self.dictionary = [] + + def loadCdic(self, cdic): + if cdic[0:8] != b"CDIC\x00\x00\x00\x10": + raise unpackException("invalid cdic header") + phrases, bits = struct.unpack_from(b">LL", cdic, 8) + n = min(1 << bits, phrases - len(self.dictionary)) + h = struct.Struct(b">H").unpack_from + + def getslice(off): + (blen,) = h(cdic, 16 + off) + slice = cdic[18 + off : 18 + off + (blen & 0x7FFF)] + return (slice, blen & 0x8000) + + self.dictionary += list(map(getslice, struct.unpack_from(bytes(">%dH" % n, "latin-1"), cdic, 16))) + + def unpack(self, data): + q = HuffcdicReader.q + + bitsleft = len(data) * 8 + data += b"\x00\x00\x00\x00\x00\x00\x00\x00" + pos = 0 + (x,) = q(data, pos) + n = 32 + + s = b"" + while True: + if n <= 0: + pos += 4 + (x,) = q(data, pos) + n += 32 + code = (x >> n) & ((1 << 32) - 1) + + codelen, term, maxcode = self.dict1[code >> 24] + if not term: + while code < self.mincode[codelen]: + codelen += 1 + maxcode = self.maxcode[codelen] + + n -= codelen + bitsleft -= codelen + if bitsleft < 0: + break + + r = (maxcode - code) >> (32 - codelen) + slice, flag = self.dictionary[r] + if not flag: + self.dictionary[r] = None + slice = self.unpack(slice) + self.dictionary[r] = (slice, 1) + s += slice + return s diff --git a/application/lib/calibre/ebooks/compression/palmdoc.py b/application/lib/calibre/ebooks/compression/palmdoc.py index 42c33dbd..1b8e26cb 100644 --- a/application/lib/calibre/ebooks/compression/palmdoc.py +++ b/application/lib/calibre/ebooks/compression/palmdoc.py @@ -8,10 +8,11 @@ from struct import pack #from calibre_extensions import cPalmdoc +from .mobi_uncompress import PalmdocReader - -#def decompress_doc(data): -# return cPalmdoc.decompress(data) +def decompress_doc(data): + return PalmdocReader().unpack(data) + #return cPalmdoc.decompress(data) def compress_doc(data): diff --git a/application/lib/calibre/ebooks/conversion/plugins/mobi_input.py b/application/lib/calibre/ebooks/conversion/plugins/mobi_input.py new file mode 100644 index 00000000..dc3a974c --- /dev/null +++ b/application/lib/calibre/ebooks/conversion/plugins/mobi_input.py @@ -0,0 +1,66 @@ +__license__ = 'GPL 3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os + +from calibre.customize.conversion import InputFormatPlugin +from calibre.ebooks import DRMError + +class MOBIInput(InputFormatPlugin): + + name = 'MOBI Input' + author = 'Kovid Goyal' + description = _('Convert MOBI files (.mobi, .prc, .azw) to HTML') + file_types = {'mobi', 'prc', 'azw', 'azw3', 'pobi'} + commit_name = 'mobi_input' + + #执行转换完成后返回生成的 opf 文件路径,只是路径,不包含文件名 + #recipes: 可以为文件名, StringIO, 或一个列表 + #output_dir: 输出目录 + #fs: plumber生成的FsDictStub实例 + #返回 opf文件的全路径名或传入的fs实例 + def convert(self, stream, opts, file_ext, log, output_dir, fs): + self.user = opts.user + self.is_kf8 = False + self.mobi_is_joint = False + + from calibre.ebooks.mobi.reader.mobi6 import MobiReader + from lxml import html + parse_cache = {} + try: + mr = MobiReader(stream, log, opts.input_encoding, opts.debug_pipeline, fs=fs) + if mr.kf8_type is None: + mr.extract_content(output_dir, parse_cache) + except DRMError: + raise + except: + mr = MobiReader(stream, log, opts.input_encoding, + opts.debug_pipeline, try_extra_data_fix=True, fs=fs) + if mr.kf8_type is None: + mr.extract_content(output_dir, parse_cache) + + if mr.kf8_type is not None: + log('Found KF8 MOBI of type %r'%mr.kf8_type) + if mr.kf8_type == 'joint': + self.mobi_is_joint = True + from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader + mr = Mobi8Reader(mr, log, fs=fs) + opf = mr(output_dir) + self.encrypted_fonts = mr.encrypted_fonts + self.is_kf8 = True + return opf + + raw = parse_cache.pop('calibre_raw_mobi_markup', False) + if raw: + if isinstance(raw, str): + raw = raw.encode('utf-8') + fs.write(os.path.join(output_dir, 'debug-raw.html'), raw, 'wb') + from calibre.ebooks.oeb.base import close_self_closing_tags + for f, root in parse_cache.items(): + raw = html.tostring(root, encoding='utf-8', method='xml', + include_meta_content_type=False) + raw = close_self_closing_tags(raw) + fs.write(os.path.join(output_dir, f), raw, 'wb') + #accelerators['pagebreaks'] = '//h:div[@class="mbp_pagebreak"]' + return fs if fs else mr.created_opf_path diff --git a/application/lib/calibre/ebooks/conversion/plumber.py b/application/lib/calibre/ebooks/conversion/plumber.py index 762f4f63..d88a9913 100644 --- a/application/lib/calibre/ebooks/conversion/plumber.py +++ b/application/lib/calibre/ebooks/conversion/plumber.py @@ -4,6 +4,7 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from PIL.Image import isImageType import os, re, sys, shutil, pprint, json, io, css_parser, logging, traceback from itertools import chain from functools import partial @@ -24,7 +25,7 @@ from polyglot.builtins import string_or_bytes from filesystem_dict import FsDictStub -from application.utils import get_directory_size +from application.utils import get_directory_size, loc_exc_pos from application.base_handler import save_delivery_log DEBUG_README=b''' @@ -397,7 +398,7 @@ def run(self): self.oeb = self.input_plugin(self.input_, self.opts, self.input_fmt, self.log, tdir, fs) except Exception as e: if 'All feeds are empty, aborting.' in str(e): - self.log.warning('Failed to execute input plugin: {}'.format(str(e))) + self.log.warning('Plumber: All feeds are empty, aborting.') else: self.log.warning('Failed to execute input plugin: {}'.format(traceback.format_exc())) fs.clear() @@ -416,11 +417,12 @@ def run(self): # return self.opts_to_mi(self.opts, self.user_metadata) if not hasattr(self.oeb, 'manifest'): #从一堆文件里面创建OEBBook实例 + fs.find_opf_path() try: - self.oeb = create_oebbook(self.log, self.oeb, self.opts, encoding=self.input_plugin.output_encoding, + self.oeb = create_oebbook(self.log, fs, self.opts, encoding=self.input_plugin.output_encoding, removed_items=getattr(self.input_plugin, 'removed_items_to_ignore', ())) - except Exception as e: - self.log.warning('Failed to create oebbook for recipes: {}'.format(str(e))) + except: + self.log.warning(loc_exc_pos('Failed to create oebbook')) fs.clear() return diff --git a/application/lib/calibre/ebooks/mobi/reader/mobi6.py b/application/lib/calibre/ebooks/mobi/reader/mobi6.py index 8b6e71f5..cea9fcff 100644 --- a/application/lib/calibre/ebooks/mobi/reader/mobi6.py +++ b/application/lib/calibre/ebooks/mobi/reader/mobi6.py @@ -49,7 +49,7 @@ class MobiReader: IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex') def __init__(self, filename_or_stream, log=None, user_encoding=None, debug=None, - try_extra_data_fix=False): + try_extra_data_fix=False, fs=None): self.log = log or default_log self.debug = debug self.embedded_mi = None @@ -74,14 +74,20 @@ def __init__(self, filename_or_stream, log=None, user_encoding=None, debug=None, self.tag_css_rules = {} self.left_margins = {} self.text_indents = {} + self.fs = fs - if hasattr(filename_or_stream, 'read'): + if isinstance(filename_or_stream, (bytes, bytearray)): + raw = filename_or_stream + elif hasattr(filename_or_stream, 'read'): stream = filename_or_stream stream.seek(0) + raw = stream.read() + elif fs: + raw = fs.read(filename_or_stream, 'rb') else: stream = open(filename_or_stream, 'rb') - - raw = stream.read() + raw = stream.read() + if raw.startswith(b'TPZ'): raise TopazError(_('This is an Amazon Topaz book. It cannot be processed.')) if raw.startswith(b'\xeaDRMION\xee'): @@ -160,6 +166,15 @@ def check_for_drm(self): name = self.name raise DRMError(name) + def write_as_utf8(self, path, data): + if isinstance(data, str): + data = data.encode('utf-8') + if self.fs: + self.fs.write(path, data, 'wb') + else: + with open(path, 'wb') as f: + f.write(data) + def extract_content(self, output_dir, parse_cache): #output_dir = os.path.abspath(output_dir) self.check_for_drm() @@ -254,7 +269,8 @@ def extract_content(self, output_dir, parse_cache): for x in b: b.remove(x) body.append(x) - root.append(head), root.append(body) + root.append(head) + root.append(body) for x in root.xpath('//script'): x.getparent().remove(x) @@ -299,38 +315,34 @@ def extract_content(self, output_dir, parse_cache): except AttributeError: pass - def write_as_utf8(path, data): - if isinstance(data, str): - data = data.encode('utf-8') - with open(path, 'wb') as f: - f.write(data) - parse_cache[htmlfile] = root self.htmlfile = htmlfile + opf_buf = io.BytesIO() ncx = io.BytesIO() opf, ncx_manifest_entry = self.create_opf(htmlfile, guide, root) self.created_opf_path = os.path.splitext(htmlfile)[0] + '.opf' - opf.render(open(self.created_opf_path, 'wb'), ncx, - ncx_manifest_entry=ncx_manifest_entry) + opf.render(opf_buf, ncx, ncx_manifest_entry=ncx_manifest_entry) + self.write_as_utf8(self.created_opf_path, opf_buf.getvalue()) ncx = ncx.getvalue() if ncx: ncx_path = os.path.join(os.path.dirname(htmlfile), 'toc.ncx') - write_as_utf8(ncx_path, ncx) + self.write_as_utf8(ncx_path, ncx) css = [self.base_css_rules, '\n\n'] for cls, rule in self.tag_css_rules.items(): css.append(f'.{cls} {{ {rule} }}\n\n') - write_as_utf8('styles.css', ''.join(css)) + self.write_as_utf8('styles.css', ''.join(css)) if self.book_header.exth is not None or self.embedded_mi is not None: self.log.debug('Creating OPF...') + opf_buf = io.BytesIO() ncx = io.BytesIO() opf, ncx_manifest_entry = self.create_opf(htmlfile, guide, root) - opf.render(open(os.path.splitext(htmlfile)[0] + '.opf', 'wb'), ncx, - ncx_manifest_entry) + opf.render(opf_buf, ncx, ncx_manifest_entry) + self.write_as_utf8(os.path.splitext(htmlfile)[0] + '.opf', opf_buf.getvalue()) ncx = ncx.getvalue() if ncx: - write_as_utf8(os.path.splitext(htmlfile)[0] + '.ncx', ncx) + self.write_as_utf8(os.path.splitext(htmlfile)[0] + '.ncx', ncx) def read_embedded_metadata(self, root, elem, guide): raw = b'\n' + \ @@ -657,7 +669,7 @@ def create_opf(self, htmlfile, guide=None, root=None): mi = getattr(self.book_header.exth, 'mi', self.embedded_mi) if mi is None: mi = MetaInformation(self.book_header.title, [_('Unknown')]) - opf = OPFCreator(os.path.dirname(htmlfile), mi) + opf = OPFCreator(os.path.dirname(htmlfile), mi, self.fs) if hasattr(self.book_header.exth, 'cover_offset'): opf.cover = 'images/%05d.jpg' % (self.book_header.exth.cover_offset + 1) elif mi.cover is not None: @@ -883,8 +895,10 @@ def add_anchors(self): def extract_images(self, processed_records, output_dir): self.log.debug('Extracting images...') - output_dir = os.path.abspath(os.path.join(output_dir, 'images')) - if not os.path.exists(output_dir): + output_dir = os.path.join(output_dir, 'images') + if self.fs: + self.fs.makedirs(output_dir) + elif not os.path.exists(output_dir): os.makedirs(output_dir) image_index = 0 self.image_names = [] @@ -922,17 +936,21 @@ def extract_images(self, processed_records, output_dir): except OSError: self.log.warn(f'Ignoring undecodeable GIF image at index {image_index}') continue - path = os.path.join(output_dir, '%05d.%s' % (image_index, imgfmt)) - image_name_map[image_index] = os.path.basename(path) - if imgfmt == 'png': - with open(path, 'wb') as f: - f.write(data) - else: + + if imgfmt != 'png': try: - save_cover_data_to(data, path, minify_to=(10000, 10000)) + imgfmt = 'jpeg' + data = save_cover_data_to(data, data_fmt=imgfmt, minify_to=(10000, 10000)) except Exception: - continue - self.image_names.append(os.path.basename(path)) + data = None + if data: + if imgfmt == 'jpeg': + imgfmt = 'jpg' + path = os.path.join(output_dir, '%05d.%s' % (image_index, imgfmt)) + image_name_map[image_index] = os.path.basename(path) + self.image_names.append(os.path.basename(path)) + self.write_as_utf8(path, data) + return image_name_map diff --git a/application/lib/calibre/ebooks/mobi/reader/mobi8.py b/application/lib/calibre/ebooks/mobi/reader/mobi8.py index c1aa6acb..f9be9e50 100644 --- a/application/lib/calibre/ebooks/mobi/reader/mobi8.py +++ b/application/lib/calibre/ebooks/mobi/reader/mobi8.py @@ -5,7 +5,7 @@ __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import struct, re, os +import struct, re, os, io from collections import namedtuple from itertools import repeat from uuid import uuid4 @@ -72,16 +72,35 @@ def get_first_resource_index(first_image_index, num_of_text_records, first_text_ class Mobi8Reader: - def __init__(self, mobi6_reader, log, for_tweak=False): + def __init__(self, mobi6_reader, log, for_tweak=False, fs=None): self.for_tweak = for_tweak self.mobi6_reader, self.log = mobi6_reader, log self.header = mobi6_reader.book_header + self.fs = fs + self.output_dir = fs.path if fs else '.' self.encrypted_fonts = [] self.id_re = re.compile(br'''<[^>]+\s(?:id|ID)\s*=\s*['"]([^'"]+)['"]''') self.name_re = re.compile(br'''<\s*a\s*\s(?:name|NAME)\s*=\s*['"]([^'"]+)['"]''') self.aid_re = re.compile(br'''<[^>]+\s(?:aid|AID)\s*=\s*['"]([^'"]+)['"]''') - def __call__(self): + def write_file(self, fname, data, mode='wb'): + fname = os.path.join(self.output_dir, fname) + if self.fs: + self.fs.write(fname, data, mode) + else: + with open(fname, mode) as f: + f.write(data) + + def read_file(self, fname, mode='rb'): + fname = os.path.join(self.output_dir, fname) + if self.fs: + return self.fs.read(fname, mode) + else: + with open(fname, mode) as f: + return f.read() + + def __call__(self, output_dir=None): + self.output_dir = output_dir or self.output_dir self.mobi6_reader.check_for_drm() self.aid_anchor_suffix = uuid4().hex.encode('utf-8') bh = self.mobi6_reader.book_header @@ -97,9 +116,8 @@ def __call__(self): self.processed_records = self.mobi6_reader.extract_text(offset=offset) self.raw_ml = self.mobi6_reader.mobi_html - with open('debug-raw.html', 'wb') as f: - f.write(self.raw_ml) - + self.write_file('debug-raw.html', self.raw_ml) + self.kf8_sections = self.mobi6_reader.sections[offset-1:] self.cover_offset = getattr(self.header.exth, 'cover_offset', None) @@ -434,9 +452,7 @@ def extract_resources(self, sections): fname_idx, font['err'])) if font['headers']: self.log.debug('Font record headers: %s'%font['headers']) - with open(href.replace('/', os.sep), 'wb') as f: - f.write(font['font_data'] if font['font_data'] else - font['raw_data']) + self.write_file(href, font['font_data'] if font['font_data'] else font['raw_data']) if font['encrypted']: self.encrypted_fonts.append(href) elif typ == b'CONT': @@ -448,16 +464,14 @@ def extract_resources(self, sections): data, imgtype = container.load_image(data) if data is not None: href = 'images/%05d.%s'%(container.resource_index, imgtype) - with open(href.replace('/', os.sep), 'wb') as f: - f.write(data) + self.write_file(href, data) elif typ == b'\xa0\xa0\xa0\xa0' and len(data) == 4 and container is not None: container.resource_index += 1 elif container is None: if not (len(data) == len(PLACEHOLDER_GIF) and data == PLACEHOLDER_GIF): imgtype = find_imgtype(data) href = 'images/%05d.%s'%(fname_idx, imgtype) - with open(href.replace('/', os.sep), 'wb') as f: - f.write(data) + self.write_file(href, data) resource_map.append(href) @@ -485,7 +499,7 @@ def write_opf(self, guide, toc, spine, resource_map): except: self.log.exception('Failed to read inline ToC') - opf = OPFCreator(os.getcwd(), mi) + opf = OPFCreator(os.getcwd(), mi, self.fs) opf.guide = guide def exclude(path): @@ -525,15 +539,17 @@ def exclude(path): if pwm is not None: opf.primary_writing_mode = pwm - with open('metadata.opf', 'wb') as of, open('toc.ncx', 'wb') as ncx: - opf.render(of, ncx, 'toc.ncx') - return 'metadata.opf' + of = io.BytesIO() + ncx = io.BytesIO() + opf.render(of, ncx, 'toc.ncx') + self.write_file('metadata.opf', of.getvalue()) + self.write_file('toc.ncx', ncx.getvalue()) + return self.fs #'metadata.opf' def read_inline_toc(self, href, frag): ans = TOC() base_href = '/'.join(href.split('/')[:-1]) - with open(href.replace('/', os.sep), 'rb') as f: - raw = f.read().decode(self.header.codec) + raw = self.read_file(href).decode(self.header.codec) root = parse_html(raw, log=self.log) body = XPath('//h:body')(root) reached = False diff --git a/application/static/base.css b/application/static/base.css index 7335963b..b136f932 100644 --- a/application/static/base.css +++ b/application/static/base.css @@ -1043,22 +1043,21 @@ div.schedule_daytimes label { } .tooltip:hover::before { - text-align: left; + text-align: center; content: attr(data-msg); position: absolute; - padding: 2px 5px; + padding: 8px; display: block; - min-width: 220px; + min-width: 200px; color: #333; - opacity: 1; background-color: #FFFCCC; border: 1px solid #333; - border-radius: 5px; - font-size: 14px; - line-height: 18px; - bottom: 25px; + border-radius: 10px; + font-size: 0.8em; + /*line-height: 1.2em;*/ + bottom: 2em; /*top: calc(-220%);*/ - right: 0px; + left: 0px; } /* 用于推送记录异常信息太长的鼠标悬停指示 */ diff --git a/application/static/base.js b/application/static/base.js index dcb0a025..4bc1bf8f 100644 --- a/application/static/base.js +++ b/application/static/base.js @@ -376,7 +376,7 @@ function PopulateMySubscribed() { } hamb_arg.push({klass: 'btn-B', title: i18n.viewSrc, icon: 'icon-source', act: "/viewsrc/" + recipe_id.replace(':', '__')}); hamb_arg.push({klass: 'btn-E', title: i18n.customizeDelivTime, icon: 'icon-schedule', act: fTpl.format('ScheduleRecipe', recipe_id, title)}); - hamb_arg.push({klass: 'btn-A', title: i18n.unsubscribe, icon: 'icon-unsubscribe', act: fTpl.format('UnsubscribeRecipe', recipe_id, title)}); + hamb_arg.push({klass: 'btn-A', title: i18n.unsubscribe, icon: 'icon-delete', act: fTpl.format('UnsubscribeRecipe', recipe_id, title)}); row_str.push(AddHamburgerButton(hamb_arg)); row_str.push(''); //console.log(row_str.join('')); diff --git a/application/static/reader.js b/application/static/reader.js index cdd910de..cb4d899d 100644 --- a/application/static/reader.js +++ b/application/static/reader.js @@ -823,9 +823,9 @@ function navClickEvent(event) { if (navTitle) { var src = navTitle.getAttribute('data-src'); var span = navTitle.querySelector('.nav-title-text'); - var text = span ? span.textContent.trim() : ''; - if (src && text) { - openArticle({title: text, src: src}); + var title = span ? span.textContent.trim() : ''; + if (src && title) { + openArticle({title: title, src: src}); } } else if (navBook) { toggleNavBook(navBook); @@ -891,8 +891,11 @@ function getBookLanguage(art) { function openArticle(article) { if (article.src) { var iframe = document.getElementById('iframe'); - //iframe.style.height = 'auto'; //规避iframe只能变大不能变小的bug - iframe.style.display = "none"; //加载完成后再显示 + var oldSrc = g_currentArticle.src ? g_currentArticle.src.replace(/#.*$/, '') : ''; + var newSrc = article.src.replace(/#.*$/, ''); + if (oldSrc != newSrc) { + iframe.style.display = "none"; //加载完成后再显示 + } iframe.src = '/reader/article/' + article.src; g_currentArticle = article; } diff --git a/application/templates/settings.html b/application/templates/settings.html index 34b4c428..3d95bc2c 100644 --- a/application/templates/settings.html +++ b/application/templates/settings.html @@ -45,7 +45,7 @@
- +
- +
- +