mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 11:25:29 -04:00
Merge branch 'dev' into fix/hide-subscribe-btn
This commit is contained in:
commit
cb3df432d0
@ -12,3 +12,4 @@ Layout/LineLength:
|
|||||||
MaxLength: 80
|
MaxLength: 80
|
||||||
Excluded:
|
Excluded:
|
||||||
- src/routes/api.cr
|
- src/routes/api.cr
|
||||||
|
- spec/plugin_spec.cr
|
||||||
|
@ -14,6 +14,7 @@ const readerComponent = () => {
|
|||||||
margin: 30,
|
margin: 30,
|
||||||
preloadLookahead: 3,
|
preloadLookahead: 3,
|
||||||
enableRightToLeft: false,
|
enableRightToLeft: false,
|
||||||
|
fitType: 'vert',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the component by fetching the page dimensions
|
* Initialize the component by fetching the page dimensions
|
||||||
@ -29,14 +30,16 @@ const readerComponent = () => {
|
|||||||
return {
|
return {
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
url: `${base_url}api/page/${tid}/${eid}/${i+1}`,
|
url: `${base_url}api/page/${tid}/${eid}/${i+1}`,
|
||||||
width: d.width,
|
width: d.width == 0 ? "100%" : d.width,
|
||||||
height: d.height,
|
height: d.height == 0 ? "100%" : d.height,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const avgRatio = this.items.reduce((acc, cur) => {
|
// Note: for image types not supported by image_size.cr, the width and height will be 0, and so `avgRatio` will be `Infinity`.
|
||||||
|
// TODO: support more image types in image_size.cr
|
||||||
|
const avgRatio = dimensions.reduce((acc, cur) => {
|
||||||
return acc + cur.height / cur.width
|
return acc + cur.height / cur.width
|
||||||
}, 0) / this.items.length;
|
}, 0) / dimensions.length;
|
||||||
|
|
||||||
console.log(avgRatio);
|
console.log(avgRatio);
|
||||||
this.longPages = avgRatio > 2;
|
this.longPages = avgRatio > 2;
|
||||||
@ -63,6 +66,11 @@ const readerComponent = () => {
|
|||||||
this.preloadImage(this.items[idx - 1].url);
|
this.preloadImage(this.items[idx - 1].url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const savedFitType = localStorage.getItem('fitType');
|
||||||
|
if (savedFitType) {
|
||||||
|
this.fitType = savedFitType;
|
||||||
|
$('#fit-select').val(savedFitType);
|
||||||
|
}
|
||||||
const savedFlipAnimation = localStorage.getItem('enableFlipAnimation');
|
const savedFlipAnimation = localStorage.getItem('enableFlipAnimation');
|
||||||
this.enableFlipAnimation = savedFlipAnimation === null || savedFlipAnimation === 'true';
|
this.enableFlipAnimation = savedFlipAnimation === null || savedFlipAnimation === 'true';
|
||||||
|
|
||||||
@ -333,6 +341,11 @@ const readerComponent = () => {
|
|||||||
this.toPage(this.selectedIndex);
|
this.toPage(this.selectedIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fitChanged(){
|
||||||
|
this.fitType = $('#fit-select').val();
|
||||||
|
localStorage.setItem('fitType', this.fitType);
|
||||||
|
},
|
||||||
|
|
||||||
preloadLookaheadChanged() {
|
preloadLookaheadChanged() {
|
||||||
localStorage.setItem('preloadLookahead', this.preloadLookahead);
|
localStorage.setItem('preloadLookahead', this.preloadLookahead);
|
||||||
},
|
},
|
||||||
|
0
spec/asset/plugins/plugin/index.js
Normal file
0
spec/asset/plugins/plugin/index.js
Normal file
6
spec/asset/plugins/plugin/info.json
Normal file
6
spec/asset/plugins/plugin/info.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"id": "test",
|
||||||
|
"title": "Test Plugin",
|
||||||
|
"placeholder": "placeholder",
|
||||||
|
"wait_seconds": 1
|
||||||
|
}
|
@ -1,14 +1,31 @@
|
|||||||
require "./spec_helper"
|
require "./spec_helper"
|
||||||
|
|
||||||
describe Config do
|
describe Config do
|
||||||
it "creates config if it does not exist" do
|
it "creates default config if it does not exist" do
|
||||||
with_default_config do |_, path|
|
with_default_config do |config, path|
|
||||||
File.exists?(path).should be_true
|
File.exists?(path).should be_true
|
||||||
|
config.port.should eq 9000
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "correctly loads config" do
|
it "correctly loads config" do
|
||||||
config = Config.load "spec/asset/test-config.yml"
|
config = Config.load "spec/asset/test-config.yml"
|
||||||
config.port.should eq 3000
|
config.port.should eq 3000
|
||||||
|
config.base_url.should eq "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "correctly reads config defaults from ENV" do
|
||||||
|
ENV["LOG_LEVEL"] = "debug"
|
||||||
|
config = Config.load "spec/asset/test-config.yml"
|
||||||
|
config.log_level.should eq "debug"
|
||||||
|
config.base_url.should eq "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "correctly handles ENV truthiness" do
|
||||||
|
ENV["CACHE_ENABLED"] = "false"
|
||||||
|
config = Config.load "spec/asset/test-config.yml"
|
||||||
|
config.cache_enabled.should be_false
|
||||||
|
config.cache_log_enabled.should be_true
|
||||||
|
config.disable_login.should be_false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
70
spec/plugin_spec.cr
Normal file
70
spec/plugin_spec.cr
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
require "./spec_helper"
|
||||||
|
|
||||||
|
describe Plugin do
|
||||||
|
describe "helper functions" do
|
||||||
|
it "mango.text" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.text('<a href="https://github.com">Click Me<a>');
|
||||||
|
JS
|
||||||
|
res.should eq "Click Me"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mango.text returns empty string when no text" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.text('<img src="https://github.com" />');
|
||||||
|
JS
|
||||||
|
res.should eq ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mango.css" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.css('<ul><li class="test">A</li><li class="test">B</li><li>C</li></ul>', 'li.test');
|
||||||
|
|
||||||
|
JS
|
||||||
|
res.should eq ["<li class=\"test\">A</li>", "<li class=\"test\">B</li>"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mango.css returns empty array when no match" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.css('<ul><li class="test">A</li><li class="test">B</li><li>C</li></ul>', 'li.noclass');
|
||||||
|
JS
|
||||||
|
res.should eq [] of String
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mango.attribute" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.attribute('<a href="https://github.com">Click Me<a>', 'href');
|
||||||
|
JS
|
||||||
|
res.should eq "https://github.com"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mango.attribute returns undefined when no match" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.attribute('<div />', 'href') === undefined;
|
||||||
|
JS
|
||||||
|
res.should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://github.com/hkalexling/Mango/issues/320
|
||||||
|
it "mango.attribute handles tags in attribute values" do
|
||||||
|
with_plugin do |plugin|
|
||||||
|
res = plugin.eval <<-JS
|
||||||
|
mango.attribute('<div data-a="<img />" data-b="test" />', 'data-b');
|
||||||
|
JS
|
||||||
|
res.should eq "test"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -3,6 +3,7 @@ require "../src/queue"
|
|||||||
require "../src/server"
|
require "../src/server"
|
||||||
require "../src/config"
|
require "../src/config"
|
||||||
require "../src/main_fiber"
|
require "../src/main_fiber"
|
||||||
|
require "../src/plugin/plugin"
|
||||||
|
|
||||||
class State
|
class State
|
||||||
@@hash = {} of String => String
|
@@hash = {} of String => String
|
||||||
@ -54,3 +55,10 @@ def with_storage
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_plugin
|
||||||
|
with_default_config do
|
||||||
|
plugin = Plugin.new "test", "spec/asset/plugins"
|
||||||
|
yield plugin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@ -1,31 +1,51 @@
|
|||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
class Config
|
class Config
|
||||||
|
private OPTIONS = {
|
||||||
|
"host" => "0.0.0.0",
|
||||||
|
"port" => 9000,
|
||||||
|
"base_url" => "/",
|
||||||
|
"session_secret" => "mango-session-secret",
|
||||||
|
"library_path" => "~/mango/library",
|
||||||
|
"library_cache_path" => "~/mango/library.yml.gz",
|
||||||
|
"db_path" => "~/mango.db",
|
||||||
|
"queue_db_path" => "~/mango/queue.db",
|
||||||
|
"scan_interval_minutes" => 5,
|
||||||
|
"thumbnail_generation_interval_hours" => 24,
|
||||||
|
"log_level" => "info",
|
||||||
|
"upload_path" => "~/mango/uploads",
|
||||||
|
"plugin_path" => "~/mango/plugins",
|
||||||
|
"download_timeout_seconds" => 30,
|
||||||
|
"cache_enabled" => true,
|
||||||
|
"cache_size_mbs" => 50,
|
||||||
|
"cache_log_enabled" => true,
|
||||||
|
"disable_login" => false,
|
||||||
|
"default_username" => "",
|
||||||
|
"auth_proxy_header_name" => "",
|
||||||
|
"plugin_update_interval_hours" => 24,
|
||||||
|
}
|
||||||
|
|
||||||
include YAML::Serializable
|
include YAML::Serializable
|
||||||
|
|
||||||
@[YAML::Field(ignore: true)]
|
@[YAML::Field(ignore: true)]
|
||||||
property path = ""
|
property path : String = ""
|
||||||
property host = "0.0.0.0"
|
|
||||||
property port : Int32 = 9000
|
# Go through the options constant above and define them as properties.
|
||||||
property base_url = "/"
|
# Allow setting the default values through environment variables.
|
||||||
property session_secret = "mango-session-secret"
|
# Overall precedence: config file > environment variable > default value
|
||||||
property library_path = "~/mango/library"
|
{% begin %}
|
||||||
property library_cache_path = "~/mango/library.yml.gz"
|
{% for k, v in OPTIONS %}
|
||||||
property db_path = "~/mango/mango.db"
|
{% if v.is_a? StringLiteral %}
|
||||||
property queue_db_path = "~/mango/queue.db"
|
property {{k.id}} : String = ENV[{{k.upcase}}]? || {{ v }}
|
||||||
property scan_interval_minutes : Int32 = 5
|
{% elsif v.is_a? NumberLiteral %}
|
||||||
property thumbnail_generation_interval_hours : Int32 = 24
|
property {{k.id}} : Int32 = (ENV[{{k.upcase}}]? || {{ v.id }}).to_i
|
||||||
property log_level = "info"
|
{% elsif v.is_a? BoolLiteral %}
|
||||||
property upload_path = "~/mango/uploads"
|
property {{k.id}} : Bool = env_is_true? {{ k.upcase }}, {{ v.id }}
|
||||||
property plugin_path = "~/mango/plugins"
|
{% else %}
|
||||||
property download_timeout_seconds : Int32 = 30
|
raise "Unknown type in config option: {{ v.class_name.id }}"
|
||||||
property cache_enabled = true
|
{% end %}
|
||||||
property cache_size_mbs = 50
|
{% end %}
|
||||||
property cache_log_enabled = true
|
{% end %}
|
||||||
property disable_login = false
|
|
||||||
property default_username = ""
|
|
||||||
property auth_proxy_header_name = ""
|
|
||||||
property plugin_update_interval_hours : Int32 = 24
|
|
||||||
|
|
||||||
@@singlet : Config?
|
@@singlet : Config?
|
||||||
|
|
||||||
@ -38,7 +58,7 @@ class Config
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.load(path : String?)
|
def self.load(path : String?)
|
||||||
path = "~/.config/mango/config.yml" if path.nil?
|
path = (ENV["CONFIG_PATH"]? || "~/.config/mango/config.yml") if path.nil?
|
||||||
cfg_path = File.expand_path path, home: true
|
cfg_path = File.expand_path path, home: true
|
||||||
if File.exists? cfg_path
|
if File.exists? cfg_path
|
||||||
config = self.from_yaml File.read cfg_path
|
config = self.from_yaml File.read cfg_path
|
||||||
|
@ -632,6 +632,16 @@ class Title
|
|||||||
|
|
||||||
if last_read_entry && last_read_entry.finished? username
|
if last_read_entry && last_read_entry.finished? username
|
||||||
last_read_entry = last_read_entry.next_entry username
|
last_read_entry = last_read_entry.next_entry username
|
||||||
|
if last_read_entry.nil?
|
||||||
|
# The last entry is finished. Return the first unfinished entry
|
||||||
|
# (if any)
|
||||||
|
sorted_entries(username).each do |e|
|
||||||
|
unless e.finished? username
|
||||||
|
last_read_entry = e
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
last_read_entry
|
last_read_entry
|
||||||
|
@ -105,9 +105,10 @@ class Plugin
|
|||||||
getter js_path = ""
|
getter js_path = ""
|
||||||
getter storage_path = ""
|
getter storage_path = ""
|
||||||
|
|
||||||
def self.build_info_ary
|
def self.build_info_ary(dir : String? = nil)
|
||||||
@@info_ary.clear
|
@@info_ary.clear
|
||||||
dir = Config.current.plugin_path
|
dir ||= Config.current.plugin_path
|
||||||
|
|
||||||
Dir.mkdir_p dir unless Dir.exists? dir
|
Dir.mkdir_p dir unless Dir.exists? dir
|
||||||
|
|
||||||
Dir.each_child dir do |f|
|
Dir.each_child dir do |f|
|
||||||
@ -160,8 +161,8 @@ class Plugin
|
|||||||
list.save
|
list.save
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(id : String)
|
def initialize(id : String, dir : String? = nil)
|
||||||
Plugin.build_info_ary
|
Plugin.build_info_ary dir
|
||||||
|
|
||||||
@info = @@info_ary.find &.id.== id
|
@info = @@info_ary.find &.id.== id
|
||||||
if @info.nil?
|
if @info.nil?
|
||||||
@ -319,7 +320,7 @@ class Plugin
|
|||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
private def eval(str)
|
def eval(str)
|
||||||
@rt.eval str
|
@rt.eval str
|
||||||
rescue e : Duktape::SyntaxError
|
rescue e : Duktape::SyntaxError
|
||||||
raise SyntaxError.new e.message
|
raise SyntaxError.new e.message
|
||||||
@ -448,9 +449,15 @@ class Plugin
|
|||||||
env = Duktape::Sandbox.new ptr
|
env = Duktape::Sandbox.new ptr
|
||||||
html = env.require_string 0
|
html = env.require_string 0
|
||||||
|
|
||||||
str = XML.parse(html).inner_text
|
begin
|
||||||
|
parser = Myhtml::Parser.new html
|
||||||
|
str = parser.body!.children.first.inner_text
|
||||||
|
|
||||||
env.push_string str
|
env.push_string str
|
||||||
|
rescue
|
||||||
|
env.push_string ""
|
||||||
|
end
|
||||||
|
|
||||||
env.call_success
|
env.call_success
|
||||||
end
|
end
|
||||||
sbx.put_prop_string -2, "text"
|
sbx.put_prop_string -2, "text"
|
||||||
@ -461,8 +468,9 @@ class Plugin
|
|||||||
name = env.require_string 1
|
name = env.require_string 1
|
||||||
|
|
||||||
begin
|
begin
|
||||||
attr = XML.parse(html).first_element_child.not_nil![name]
|
parser = Myhtml::Parser.new html
|
||||||
env.push_string attr
|
attr = parser.body!.children.first.attribute_by name
|
||||||
|
env.push_string attr.not_nil!
|
||||||
rescue
|
rescue
|
||||||
env.push_undefined
|
env.push_undefined
|
||||||
end
|
end
|
||||||
|
@ -64,11 +64,12 @@ class Dir
|
|||||||
path = File.join dirname, fn
|
path = File.join dirname, fn
|
||||||
if File.directory? path
|
if File.directory? path
|
||||||
signatures << Dir.contents_signature path, cache
|
signatures << Dir.contents_signature path, cache
|
||||||
signatures << fn if DirEntry.is_valid? path
|
|
||||||
else
|
else
|
||||||
# Only add its signature value to `signatures` when it is a
|
# Only add its signature value to `signatures` when it is a
|
||||||
# supported file
|
# supported file
|
||||||
signatures << fn if ArchiveEntry.is_valid? fn
|
if ArchiveEntry.is_valid?(fn) || is_supported_image_file(fn)
|
||||||
|
signatures << fn
|
||||||
|
end
|
||||||
end
|
end
|
||||||
Fiber.yield
|
Fiber.yield
|
||||||
end
|
end
|
||||||
|
@ -11,6 +11,7 @@ SUPPORTED_IMG_TYPES = %w(
|
|||||||
image/avif
|
image/avif
|
||||||
image/gif
|
image/gif
|
||||||
image/svg+xml
|
image/svg+xml
|
||||||
|
image/jxl
|
||||||
)
|
)
|
||||||
|
|
||||||
def random_str
|
def random_str
|
||||||
@ -49,6 +50,7 @@ def register_mime_types
|
|||||||
# defiend by Crystal in `MIME.DEFAULT_TYPES`
|
# defiend by Crystal in `MIME.DEFAULT_TYPES`
|
||||||
".apng" => "image/apng",
|
".apng" => "image/apng",
|
||||||
".avif" => "image/avif",
|
".avif" => "image/avif",
|
||||||
|
".jxl" => "image/jxl",
|
||||||
}.each do |k, v|
|
}.each do |k, v|
|
||||||
MIME.register k, v
|
MIME.register k, v
|
||||||
end
|
end
|
||||||
@ -93,9 +95,9 @@ class String
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def env_is_true?(key : String) : Bool
|
def env_is_true?(key : String, default : Bool = false) : Bool
|
||||||
val = ENV[key.upcase]? || ENV[key.downcase]?
|
val = ENV[key.upcase]? || ENV[key.downcase]?
|
||||||
return false unless val
|
return default unless val
|
||||||
val.downcase.in? "1", "true"
|
val.downcase.in? "1", "true"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<%= render_component "head" %>
|
<%= render_component "head" %>
|
||||||
|
|
||||||
<body style="position:relative;" x-data="readerComponent()" x-init="init($nextTick)" @resize.window="resized()">
|
<body style="position:relative;" x-data="readerComponent()" x-init="init($nextTick)" @resize.window="resized()">
|
||||||
<div class="uk-section uk-section-default uk-section-small reader-bg" :style="mode === 'continuous' ? '' : 'padding:0'">
|
<div class="uk-section uk-section-default uk-section-small reader-bg" :style="mode === 'continuous' ? '' : 'padding:0; position: relative;'">
|
||||||
|
|
||||||
<div @keydown.window.debounce="keyHandler($event)"></div>
|
<div @keydown.window.debounce="keyHandler($event)"></div>
|
||||||
|
|
||||||
@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:class="{'uk-container': true, 'uk-container-small': mode === 'continuous', 'uk-container-expand': mode !== 'continuous'}">
|
:class="{'uk-container': true, 'uk-container-small': mode === 'continuous', 'uk-container-expand': mode !== 'continuous'}" style="width: fit-content;">
|
||||||
<div x-show="!loading && mode === 'continuous'" x-cloak>
|
<div x-show="!loading && mode === 'continuous'" x-cloak>
|
||||||
<template x-if="!loading && mode === 'continuous'" x-for="item in items">
|
<template x-if="!loading && mode === 'continuous'" x-for="item in items">
|
||||||
<img
|
<img
|
||||||
@ -40,18 +40,18 @@
|
|||||||
<%- end -%>
|
<%- end -%>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div x-cloak x-show="!loading && mode !== 'continuous'" class="uk-flex uk-flex-middle" style="height:100vh">
|
<div x-cloak x-show="!loading && mode !== 'continuous'" class="uk-flex uk-flex-middle" :style="`height:${fitType === 'vert' ? '100vh' : ''}; min-width: fit-content;`">
|
||||||
|
|
||||||
<img uk-img :class="{
|
<img uk-img :class="{
|
||||||
'uk-align-center': true,
|
'uk-align-center': true,
|
||||||
'uk-animation-slide-left': flipAnimation === 'left',
|
'uk-animation-slide-left': flipAnimation === 'left',
|
||||||
'uk-animation-slide-right': flipAnimation === 'right'
|
'uk-animation-slide-right': flipAnimation === 'right'
|
||||||
}" :data-src="curItem.url" :width="curItem.width" :height="curItem.height" :id="curItem.id" @click="clickImage($event)" :style="`
|
}" :data-src="curItem.url" :width="curItem.width" :height="curItem.height" :id="curItem.id" @click="clickImage($event)" :style="`
|
||||||
width:${mode === 'width' ? '100vw' : 'auto'};
|
width:${fitType === 'horz' ? '100vw' : 'auto'};
|
||||||
height:${mode === 'height' ? '100vh' : 'auto'};
|
height:${fitType === 'vert' ? '100vh' : 'auto'};
|
||||||
margin-bottom:0;
|
margin-bottom:0;
|
||||||
max-width:100%;
|
max-width:${fitType === 'horz' ? '100%' : fitType === 'vert' ? '' : 'none' };
|
||||||
max-height:100%;
|
max-height:${fitType === 'vert' ? '100%' : fitType === 'horz' ? '' : 'none'};
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
`" />
|
`" />
|
||||||
|
|
||||||
@ -94,6 +94,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="uk-margin" x-show="mode !== 'continuous'">
|
||||||
|
<label class="uk-form-label" for="mode-select">Page fit</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<select id="fit-select" class="uk-select" @change="fitChanged()">
|
||||||
|
<option value="vert">Fit height</option>
|
||||||
|
<option value="horz">Fit width</option>
|
||||||
|
<option value="real">Real size</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="uk-margin" x-show="mode === 'continuous'">
|
<div class="uk-margin" x-show="mode === 'continuous'">
|
||||||
<label class="uk-form-label" for="margin-range" x-text="`Page Margin: ${margin}px`"></label>
|
<label class="uk-form-label" for="margin-range" x-text="`Page Margin: ${margin}px`"></label>
|
||||||
<div class="uk-form-controls">
|
<div class="uk-form-controls">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user