add rtl and vertical article text directions

This commit is contained in:
Bruce Liu 2021-12-16 10:14:43 +08:00
parent 4e842e6420
commit cf38cc00c8
13 changed files with 140 additions and 18 deletions

View File

@ -2,14 +2,25 @@
html,
body {
margin: 0;
font-family: "Segoe UI", "Source Han Sans SC Regular", "Microsoft YaHei",
sans-serif;
}
html {
body {
padding: 12px 96px 32px;
overflow: hidden scroll;
}
body.rtl {
direction: rtl;
}
body.vertical {
padding: 32px;
padding-right: 96px;
writing-mode: vertical-rl;
overflow: scroll hidden;
}
:root {
margin: 12px 96px 32px;
--gray: #484644;
--primary: #0078d4;
--primary-alt: #004578;
@ -58,6 +69,11 @@ a:active {
margin: 0 auto;
display: none;
}
body.vertical #main {
max-width: unset;
max-height: 700px;
margin: auto 0;
}
#main.show {
display: block;
animation-name: fadeIn;
@ -80,12 +96,21 @@ a:active {
article {
line-height: 1.6;
}
body.vertical article {
line-height: 1.5;
}
body.vertical article p {
text-indent: 2rem;
}
article * {
max-width: 100%;
}
article img {
height: auto;
}
body.vertical article img {
max-height: 75%;
}
article figure {
margin: 16px 0;
text-align: center;

View File

@ -3,14 +3,14 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src-elem 'sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=' 'sha256-9YXu4Ifpt+hDzuBhE+vFtXKt1ZRbo/CkuUY4VX4dZyE='; img-src http: https: data:; style-src 'self' 'unsafe-inline'; frame-src http: https:; media-src http: https:; connect-src https: http:">
content="default-src 'none'; script-src-elem 'sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=' 'sha256-OOzJH14UHiDzBx2LDF/KOAR+BVrHdNzZ2fZvPaqCMhY='; img-src http: https: data:; style-src 'self' 'unsafe-inline'; frame-src http: https:; media-src http: https:; connect-src https: http:">
<title>Article</title>
<link rel="stylesheet" href="article.css" />
<script integrity="sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=" src="mercury.web.js"></script>
</head>
<body>
<div id="main"></div>
<script integrity="sha256-9YXu4Ifpt+hDzuBhE+vFtXKt1ZRbo/CkuUY4VX4dZyE=" src="article.js"></script>
<script integrity="sha256-OOzJH14UHiDzBx2LDF/KOAR+BVrHdNzZ2fZvPaqCMhY=" src="article.js"></script>
<!-- Run "cat article.js | openssl dgst -sha256 -binary | openssl enc -base64 -A" for hash -->
</body>
</html>

View File

@ -2,6 +2,15 @@ function get(name) {
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
return decodeURIComponent(name[1]);
}
let dir = get("d")
if (dir === "1") {
document.body.classList.add("rtl")
} else if (dir === "2") {
document.body.classList.add("vertical")
document.body.addEventListener("wheel", (evt) => {
document.scrollingElement.scrollLeft -= evt.deltaY;
});
}
async function getArticle(url) {
let article = get("a")
if (get("m") === "1") {
@ -32,4 +41,3 @@ getArticle(url).then(article => {
main.innerHTML = dom.body.innerHTML
main.classList.add("show")
})

View File

@ -12,7 +12,7 @@ import {
Icon,
Link,
} from "@fluentui/react"
import { RSSSource, SourceOpenTarget } from "../scripts/models/source"
import { RSSSource, SourceOpenTarget, SourceTextDirection } from "../scripts/models/source"
import { shareSubmenu } from "./context-menu"
import { platformCtrl, decodeFetchResponse } from "../scripts/utils"
@ -31,6 +31,10 @@ type ArticleProps = {
textMenu: (position: [number, number], text: string, url: string) => void
imageMenu: (position: [number, number]) => void
dismissContextMenu: () => void
updateSourceTextDirection: (
source: RSSSource,
direction: SourceTextDirection
) => void
}
type ArticleState = {
@ -82,6 +86,39 @@ class Article extends React.Component<ArticleProps, ArticleState> {
})),
})
updateTextDirection = (direction: SourceTextDirection) => {
this.props.updateSourceTextDirection(this.props.source, direction)
}
directionMenuProps = (): IContextualMenuProps => ({
items: [
{
key: "LTR",
text: intl.get("article.LTR"),
iconProps: { iconName: "Forward" },
canCheck: true,
checked: this.props.source.textDir === SourceTextDirection.LTR,
onClick: () => this.updateTextDirection(SourceTextDirection.LTR),
},
{
key: "RTL",
text: intl.get("article.RTL"),
iconProps: { iconName: "Back" },
canCheck: true,
checked: this.props.source.textDir === SourceTextDirection.RTL,
onClick: () => this.updateTextDirection(SourceTextDirection.RTL),
},
{
key: "Vertical",
text: intl.get("article.Vertical"),
iconProps: { iconName: "Down" },
canCheck: true,
checked: this.props.source.textDir === SourceTextDirection.Vertical,
onClick: () => this.updateTextDirection(SourceTextDirection.Vertical),
},
]
})
moreMenuProps = (): IContextualMenuProps => ({
items: [
{
@ -122,6 +159,13 @@ class Article extends React.Component<ArticleProps, ArticleState> {
disabled: this.state.loadWebpage,
subMenuProps: this.fontMenuProps(),
},
{
key: "directionMenu",
text: intl.get("article.textDir"),
iconProps: { iconName: "ChangeEntitlements" },
disabled: this.state.loadWebpage,
subMenuProps: this.directionMenuProps(),
},
{
key: "divider_1",
itemType: ContextualMenuItemType.Divider,
@ -290,7 +334,11 @@ class Article extends React.Component<ArticleProps, ArticleState> {
</>
)
)
return `article/article.html?a=${a}&h=${h}&s=${this.state.fontSize}&u=${
return `article/article.html?a=${a}&h=${h}&s=${
this.state.fontSize
}&d=${
this.props.source.textDir
}&u=${
this.props.item.link
}&m=${this.state.loadFull ? 1 : 0}`
}
@ -400,7 +448,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
? this.props.item.link
: this.articleView()
}
webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required"
webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required,nativeWindowOpen=false"
partition={this.state.loadWebpage ? "sandbox" : undefined}
/>
)}

View File

@ -17,6 +17,7 @@ import {
closeContextMenu,
openImageMenu,
} from "../scripts/models/app"
import { RSSSource, SourceTextDirection, updateSource } from "../scripts/models/source"
type ArticleContainerProps = {
itemId: number
@ -58,6 +59,14 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
imageMenu: (position: [number, number]) =>
dispatch(openImageMenu(position)),
dismissContextMenu: () => dispatch(closeContextMenu()),
updateSourceTextDirection: (
source: RSSSource,
direction: SourceTextDirection
) => {
dispatch(
updateSource({ ...source, textDir: direction } as RSSSource)
)
},
}
}

View File

@ -5,7 +5,6 @@ import {
app,
session,
clipboard,
TouchBar,
} from "electron"
import { WindowManager } from "./window"
import fs = require("fs")
@ -28,11 +27,12 @@ export function setUtilsListeners(manager: WindowManager) {
}
app.on("web-contents-created", (_, contents) => {
// TODO: Use contents.setWindowOpenHandler instead of new-window listener
contents.on("new-window", (event, url, _, disposition) => {
if (manager.hasWindow()) event.preventDefault()
contents.setWindowOpenHandler((details) => {
if (contents.getType() === "webview")
openExternal(url, disposition === "background-tab")
openExternal(details.url, details.disposition === "background-tab")
return {
action: manager.hasWindow() ? "deny" : "allow",
}
})
contents.on("will-navigate", (event, url) => {
event.preventDefault()

View File

@ -69,6 +69,7 @@ export class WindowManager {
app.getAppPath(),
(app.isPackaged ? "dist/" : "") + "preload.js"
),
nativeWindowOpen: false,
},
})
this.mainWindowState.manage(this.mainWindow)

View File

@ -4,7 +4,7 @@ import lf from "lovefield"
import { RSSSource } from "./models/source"
import { RSSItem } from "./models/item"
const sdbSchema = lf.schema.create("sourcesDB", 1)
const sdbSchema = lf.schema.create("sourcesDB", 2)
sdbSchema
.createTable("sources")
.addColumn("sid", lf.Type.INTEGER)
@ -17,6 +17,7 @@ sdbSchema
.addColumn("serviceRef", lf.Type.STRING)
.addColumn("fetchFrequency", lf.Type.NUMBER)
.addColumn("rules", lf.Type.OBJECT)
.addColumn("textDir", lf.Type.NUMBER)
.addNullable(["iconurl", "serviceRef", "rules"])
.addIndex("idxURL", ["url"], true)
@ -48,8 +49,15 @@ export let sources: lf.schema.Table
export let itemsDB: lf.Database
export let items: lf.schema.Table
async function onUpgradeSourceDB(rawDb: lf.raw.BackStore) {
const version = rawDb.getVersion()
if (version < 2) {
await rawDb.addTableColumn("sources", "textDir", 0)
}
}
export async function init() {
sourcesDB = await sdbSchema.connect()
sourcesDB = await sdbSchema.connect({ onUpgrade: onUpgradeSourceDB })
sources = sourcesDB.getSchema().table("sources")
itemsDB = await idbSchema.connect()
items = itemsDB.getSchema().table("items")
@ -90,6 +98,7 @@ async function migrateNeDB() {
// @ts-ignore
delete doc._id
if (!doc.fetchFrequency) doc.fetchFrequency = 0
doc.textDir = 0
return sources.createRow(doc)
})
const iRows = itemDocs.map(doc => {

View File

@ -66,7 +66,11 @@
"loadWebpage": "Load webpage",
"loadFull": "Load full content",
"notify": "Notify if fetched in background",
"dontNotify": "Don't notify"
"dontNotify": "Don't notify",
"textDir": "Text direction",
"LTR": "Left-to-right",
"RTL": "Right-to-left",
"Vertical": "Vertical"
},
"context": {
"share": "Share",

View File

@ -66,7 +66,11 @@
"loadWebpage": "加载网页",
"loadFull": "抓取全文",
"notify": "后台抓取时发送通知",
"dontNotify": "不发送通知"
"dontNotify": "不发送通知",
"textDir": "文本方向",
"LTR": "从左到右",
"RTL": "从右到左",
"Vertical": "纵书"
},
"context": {
"share": "分享",

View File

@ -66,7 +66,11 @@
"loadWebpage": "載入網頁",
"loadFull": "抓取全文",
"notify": "後臺抓取時傳送通知",
"dontNotify": "不傳送通知"
"dontNotify": "不傳送通知",
"textDir": "文本方向",
"LTR": "從左到右",
"RTL": "從右到左",
"Vertical": "縱書"
},
"context": {
"share": "分享",

View File

@ -23,6 +23,12 @@ export const enum SourceOpenTarget {
FullContent,
}
export const enum SourceTextDirection {
LTR,
RTL,
Vertical,
}
export class RSSSource {
sid: number
url: string
@ -34,6 +40,7 @@ export class RSSSource {
serviceRef?: string
fetchFrequency: number // in minutes
rules?: SourceRule[]
textDir: SourceTextDirection
constructor(url: string, name: string = null) {
this.url = url
@ -41,6 +48,7 @@ export class RSSSource {
this.openTarget = SourceOpenTarget.Local
this.lastFetched = new Date()
this.fetchFrequency = 0
this.textDir = SourceTextDirection.LTR
}
static async fetchMetaData(source: RSSSource) {

View File

@ -3,6 +3,7 @@ import { IPartialTheme, loadTheme } from "@fluentui/react"
import locales from "./i18n/_locales"
import { ThemeSettings } from "../schema-types"
import intl from "react-intl-universal"
import { SourceTextDirection } from "./models/source"
const lightTheme: IPartialTheme = {
defaultFontStyle: {
@ -119,6 +120,7 @@ export async function importAll() {
} else {
const sRows = configs.lovefield.sources.map(s => {
s.lastFetched = new Date(s.lastFetched)
if (!s.textDir) s.textDir = SourceTextDirection.LTR
return db.sources.createRow(s)
})
const iRows = configs.lovefield.items.map(i => {