mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-03 12:38:26 +01:00
Compare commits
3 Commits
chore/secu
...
v0.54.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46bd2a784e | ||
|
|
0a36fdfbec | ||
|
|
45d1f752d6 |
5
.github/workflows/conflict.yml
vendored
5
.github/workflows/conflict.yml
vendored
@@ -3,11 +3,6 @@ name: 🏷️🔀Merge Conflict Labeler
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [develop]
|
branches: [develop]
|
||||||
# SECURITY: pull_request_target runs with the base repo's write token and secrets.
|
|
||||||
# This job only labels via the API and is safe ONLY because it never checks out or
|
|
||||||
# runs the PR head's code. NEVER add `actions/checkout` of the PR head (or any `run:`
|
|
||||||
# that interpolates PR-controlled data) to this workflow — that would turn it into a
|
|
||||||
# full repo-compromise vector.
|
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
branches: [develop]
|
branches: [develop]
|
||||||
types: [synchronize]
|
types: [synchronize]
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ export default function IndexLayout() {
|
|||||||
open={dropdownOpen}
|
open={dropdownOpen}
|
||||||
onOpenChange={setDropdownOpen}
|
onOpenChange={setDropdownOpen}
|
||||||
trigger={
|
trigger={
|
||||||
<View className='pl-1.5'>
|
<View>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='ellipsis-horizontal-outline'
|
name='ellipsis-horizontal-outline'
|
||||||
size={24}
|
size={24}
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ const HomeMobile = () => {
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
router.push("/(auth)/downloads");
|
router.push("/(auth)/downloads");
|
||||||
}}
|
}}
|
||||||
className='ml-1.5'
|
|
||||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||||
>
|
>
|
||||||
<Feather
|
<Feather
|
||||||
|
|||||||
@@ -401,10 +401,6 @@ export const TVJellyseerrSearchResults: React.FC<
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const hasMovies = movieResults && movieResults.length > 0;
|
|
||||||
const hasTv = tvResults && tvResults.length > 0;
|
|
||||||
const hasPersons = personResults && personResults.length > 0;
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -431,22 +427,26 @@ export const TVJellyseerrSearchResults: React.FC<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
{/* No section requests `hasTVPreferredFocus`: the native search field
|
||||||
|
keeps focus while typing, otherwise the first result would re-grab
|
||||||
|
focus on every keystroke as results re-render. The user navigates
|
||||||
|
down to the grid manually. */}
|
||||||
<TVJellyseerrMovieSection
|
<TVJellyseerrMovieSection
|
||||||
title={t("search.request_movies")}
|
title={t("search.request_movies")}
|
||||||
items={movieResults}
|
items={movieResults}
|
||||||
isFirstSection={hasMovies}
|
isFirstSection={false}
|
||||||
onItemPress={onMoviePress}
|
onItemPress={onMoviePress}
|
||||||
/>
|
/>
|
||||||
<TVJellyseerrTvSection
|
<TVJellyseerrTvSection
|
||||||
title={t("search.request_series")}
|
title={t("search.request_series")}
|
||||||
items={tvResults}
|
items={tvResults}
|
||||||
isFirstSection={!hasMovies && hasTv}
|
isFirstSection={false}
|
||||||
onItemPress={onTvPress}
|
onItemPress={onTvPress}
|
||||||
/>
|
/>
|
||||||
<TVJellyseerrPersonSection
|
<TVJellyseerrPersonSection
|
||||||
title={t("search.actors")}
|
title={t("search.actors")}
|
||||||
items={personResults}
|
items={personResults}
|
||||||
isFirstSection={!hasMovies && !hasTv && hasPersons}
|
isFirstSection={false}
|
||||||
onItemPress={onPersonPress}
|
onItemPress={onPersonPress}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -235,10 +235,13 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
|||||||
module). It renders the native search bar + grid keyboard and
|
module). It renders the native search bar + grid keyboard and
|
||||||
forwards typed text into the existing query pipeline via setSearch;
|
forwards typed text into the existing query pipeline via setSearch;
|
||||||
our own results grid renders below. */}
|
our own results grid renders below. */}
|
||||||
|
{/* No horizontal margin here: the native tvOS search bar centers itself
|
||||||
|
and renders a trailing "Hold to Dictate in <Language>" hint. Extra
|
||||||
|
margins squeeze the bar's width and clip that trailing hint, so let
|
||||||
|
the native view span the full width and own its own insets. */}
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
marginHorizontal: HORIZONTAL_PADDING,
|
|
||||||
height: SEARCH_AREA_HEIGHT,
|
height: SEARCH_AREA_HEIGHT,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -280,13 +283,17 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
|||||||
{/* Library Search Results */}
|
{/* Library Search Results */}
|
||||||
{isLibraryMode && !loading && (
|
{isLibraryMode && !loading && (
|
||||||
<View style={{ gap: SECTION_GAP }}>
|
<View style={{ gap: SECTION_GAP }}>
|
||||||
{sections.map((section, index) => (
|
{sections.map((section) => (
|
||||||
<TVSearchSection
|
<TVSearchSection
|
||||||
key={section.key}
|
key={section.key}
|
||||||
title={section.title}
|
title={section.title}
|
||||||
items={section.items!}
|
items={section.items!}
|
||||||
orientation={section.orientation || "vertical"}
|
orientation={section.orientation || "vertical"}
|
||||||
isFirstSection={index === 0}
|
// Never auto-focus a result. The native search field owns focus
|
||||||
|
// while typing; `hasTVPreferredFocus` here would re-grab focus on
|
||||||
|
// every keystroke as results re-render. User navigates down to the
|
||||||
|
// grid manually.
|
||||||
|
isFirstSection={false}
|
||||||
onItemPress={onItemPress}
|
onItemPress={onItemPress}
|
||||||
onItemLongPress={onItemLongPress}
|
onItemLongPress={onItemLongPress}
|
||||||
imageUrlGetter={
|
imageUrlGetter={
|
||||||
|
|||||||
@@ -297,12 +297,12 @@ export const TVSearchSection: React.FC<TVSearchSectionProps> = ({
|
|||||||
removeClippedSubviews={false}
|
removeClippedSubviews={false}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
style={{ overflow: "visible" }}
|
style={{ overflow: "visible" }}
|
||||||
contentInset={{
|
// Edge padding via contentContainerStyle, NOT contentInset+contentOffset.
|
||||||
left: edgePadding,
|
// contentOffset only applies on initial mount; since this FlatList is
|
||||||
right: edgePadding,
|
// reused across searches (stable key), a second search left the inset
|
||||||
}}
|
// without the offset and the grid snapped flush to the left edge.
|
||||||
contentOffset={{ x: -edgePadding, y: 0 }}
|
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
|
paddingHorizontal: edgePadding,
|
||||||
paddingVertical: SCALE_PADDING,
|
paddingVertical: SCALE_PADDING,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Local helper: fast-forward master into develop and back. Aborts on any failure and
|
[[ -z $(git status --porcelain) ]] &&
|
||||||
# restores the branch you started on. Not used in CI.
|
git checkout master &&
|
||||||
set -euo pipefail
|
git pull --ff-only &&
|
||||||
|
git checkout develop &&
|
||||||
if [[ -n $(git status --porcelain) ]]; then
|
git merge master &&
|
||||||
echo "Error: working tree is not clean — commit or stash first." >&2
|
git push --follow-tags &&
|
||||||
exit 1
|
git checkout master &&
|
||||||
fi
|
git merge develop --ff-only &&
|
||||||
|
git push &&
|
||||||
start_branch=$(git rev-parse --abbrev-ref HEAD)
|
git checkout develop ||
|
||||||
trap 'git checkout "$start_branch" >/dev/null 2>&1 || true' EXIT
|
(echo "Error: Failed to merge" && exit 1)
|
||||||
|
|
||||||
git checkout master
|
|
||||||
git pull --ff-only
|
|
||||||
git checkout develop
|
|
||||||
git merge master
|
|
||||||
git push --follow-tags
|
|
||||||
git checkout master
|
|
||||||
git merge develop --ff-only
|
|
||||||
git push
|
|
||||||
git checkout develop
|
|
||||||
@@ -1,28 +1,62 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Symlinks the platform-specific native dirs to `ios` / `android` depending on EXPO_TV.
|
const _fs = require("node:fs");
|
||||||
// Uses fs APIs (no shell) so there is no command-injection surface.
|
|
||||||
|
|
||||||
const fs = require("node:fs");
|
|
||||||
const path = require("node:path");
|
const path = require("node:path");
|
||||||
|
const process = require("node:process");
|
||||||
|
const { execSync } = require("node:child_process");
|
||||||
|
|
||||||
const root = process.cwd();
|
const root = process.cwd();
|
||||||
const isTV = process.env.EXPO_TV && process.env.EXPO_TV !== "0";
|
// const tvosPath = path.join(root, 'iostv');
|
||||||
|
// const iosPath = path.join(root, 'iosmobile');
|
||||||
|
// const androidPath = path.join(root, 'androidmobile');
|
||||||
|
// const androidTVPath = path.join(root, 'androidtv');
|
||||||
|
// const device = process.argv[2];
|
||||||
|
// const platform = process.argv[2];
|
||||||
|
const isTV = process.env.EXPO_TV || false;
|
||||||
|
|
||||||
const links = isTV
|
const paths = new Map([
|
||||||
? { ios: path.join(root, "iostv"), android: path.join(root, "androidtv") }
|
["tvos", path.join(root, "iostv")],
|
||||||
: {
|
["ios", path.join(root, "iosmobile")],
|
||||||
ios: path.join(root, "iosmobile"),
|
["android", path.join(root, "androidmobile")],
|
||||||
android: path.join(root, "androidmobile"),
|
["androidtv", path.join(root, "androidtv")],
|
||||||
};
|
]);
|
||||||
|
|
||||||
for (const [link, target] of Object.entries(links)) {
|
// const platformPath = paths.get(platform);
|
||||||
fs.mkdirSync(target, { recursive: true });
|
|
||||||
try {
|
if (isTV) {
|
||||||
fs.unlinkSync(link); // replace an existing symlink/file (ln -nsf)
|
stdout = execSync(
|
||||||
} catch {
|
`mkdir -p ${paths.get("tvos")}; ln -nsf ${paths.get("tvos")} ios`,
|
||||||
// nothing to remove
|
);
|
||||||
}
|
console.log(stdout.toString());
|
||||||
fs.symlinkSync(target, link);
|
stdout = execSync(
|
||||||
console.log(`${link} -> ${target}`);
|
`mkdir -p ${paths.get("androidtv")}; ln -nsf ${paths.get(
|
||||||
|
"androidtv",
|
||||||
|
)} android`,
|
||||||
|
);
|
||||||
|
console.log(stdout.toString());
|
||||||
|
} else {
|
||||||
|
stdout = execSync(
|
||||||
|
`mkdir -p ${paths.get("ios")}; ln -nsf ${paths.get("ios")} ios`,
|
||||||
|
);
|
||||||
|
console.log(stdout.toString());
|
||||||
|
stdout = execSync(
|
||||||
|
`mkdir -p ${paths.get("android")}; ln -nsf ${paths.get("android")} android`,
|
||||||
|
);
|
||||||
|
console.log(stdout.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// target = "";
|
||||||
|
// switch (platform) {
|
||||||
|
// case "tvos":
|
||||||
|
// target = "ios";
|
||||||
|
// break;
|
||||||
|
// case "ios":
|
||||||
|
// target = "ios";
|
||||||
|
// break;
|
||||||
|
// case "android":
|
||||||
|
// target = "android";
|
||||||
|
// break;
|
||||||
|
// case "androidtv":
|
||||||
|
// target = "android";
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user